【问题标题】:Using for loop to add tasks for creating a task scheduler that limits concurrency使用for循环添加任务,创建限制并发的任务调度器
【发布时间】:2014-09-15 15:54:56
【问题描述】:

我按照MSDN 的示例制作了自己的演示,用于创建限制并发的任务调度程序。 maxDegreeOfParallelism 设置为 2。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskSchedulerThatLimitsConcurrency
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a scheduler that uses two threads. 
            LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(2);
            List<Task> tasks = new List<Task>();

            // Create a TaskFactory and pass it our custom scheduler. 
            TaskFactory factory = new TaskFactory(lcts);
            CancellationTokenSource cts = new CancellationTokenSource();

             // Use our factory to run a set of tasks. 
            Object lockObj = new Object();
            for (int i = 0; i < 4; i++)
            {
                var myFactory = new TaskFactory(lcts);
                Task t = myFactory.StartNew(() =>
                {
                    if (cts.IsCancellationRequested)
                        return;
                    lock (lockObj)
                    {
                        MakeTest(i, 1);
                    }
                }, cts.Token);
                tasks.Add(t);
            }
            for (int i = 0; i < 4; i++)
            {
                var myFactory = new TaskFactory(lcts);
                Task t1 = myFactory.StartNew(() =>
                {
                    if (cts.IsCancellationRequested)
                        return;
                    lock (lockObj)
                    {
                        MakeTest(i, 2);
                    }
                }, cts.Token);
                tasks.Add(t1);
            }
            // Wait for the tasks to complete before displaying a completion message.
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine("\n\nSuccessful completion.");
            Console.Read();
        }

        private static void MakeTest(int i, int p)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(i.ToString() + "_" + p.ToString());
            Console.WriteLine(sb.ToString());
        }
    }
}

结果是

任务 t1 和 t2 几乎相同。区别在于MakeTest(i, 1)MakeTest(i, 2)。我想知道我可以使用第二个 for 循环来添加任务吗?

我使用了下面的代码,但显然结果是错误的。

 for (int j = 0; j < 2; j++)
        {
            for (int i = 0; i < 4; i++)
            {
                var myFactory = new TaskFactory(lcts);
                Task t1 = myFactory.StartNew(() =>
                {
                    if (cts.IsCancellationRequested)
                        return;
                    lock (lockObj)
                    {
                        MakeTest(i, j+1);
                    }
                }, cts.Token);
                tasks.Add(t1);
            }
        }

结果:

问题:

  1. 为什么我的原始代码没有输出“0_1”、“1_1”等正确结果?
  2. 如果在我修改的代码中使用第二个循环,如何生成正确的结果?因为在我的真实情况下,max DegreeOfParallelism 是一个很大的数字。我无法将任务列表一一添加为MSDN example. 我以为我必须使用for循环。

【问题讨论】:

  • "maxDegreeOfParallelism 是一个很大的数字" 什么情况下需要您将并发线程的数量“限制”为一个很大的数字。我不是专家,但如果这个数字高于底层硬件/虚拟机可以同时运行的最大线程数,那么它肯定不会做任何事情。

标签: c# .net-4.0 task-parallel-library .net-4.5


【解决方案1】:

您传递给 StartNew 的 lambda 在变量 ij 上创建了一个闭包。因为它是封闭的 变量 而不是该变量的 MakeTest() 将传递 ij 的值 at调用 MakeTest() 的时间,而不是调用 StartNew 时的值。假设您的 for 循环在第一个线程到达代码中的那个点之前完成,您将始终获得 ij 在通过 for 时所具有的 last 值循环。

有关更多信息,请参阅 Eric Lippert 的文章:http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

一个简单的解决方法是在 for 循环中声明新变量。由于这些变量将在循环内限定范围,因此捕获的值将保持该变量在该特定迭代期间具有的值:

for (int j = 0; j < 2; j++)
{
    for (int i = 0; i < 4; i++)
    {
        var capturedI = i;
        var capturedJ = j;
        ...
                    MakeTest(capturedI, capturedJ+1);
        ...

【讨论】:

  • 那么如何快速修复呢?
猜你喜欢
  • 2014-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多