使用for循环添加任务以创建限制并发的任务调度程序

时间:2014-09-15 15:54:56

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

我按照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());
        }
    }
}

结果是

1

任务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);
            }
        }

结果:

2

问题:

  1. 为什么我的原始代码没有输出正确的结果,例如&#34; 0_1&#34;,&#34; 1_1&#34;等等?
  2. 如果在我的修改代码中使用第二个循环,如何生成正确的结果?因为在我的实际情况中,最大的DegreeOfParallelism是一个很大的数字。我无法逐个添加到任务列表MSDN example.我认为我必须使用for循环。

1 个答案:

答案 0 :(得分:2)

您传递给StartNew的lambda会在变量ij上创建一个闭包。由于变量是关闭的,而不是该变量的,因此MakeTest()将传递i的值调用MakeTest()时{1}}和j ,而不是调用StartNew 时的值。假设您的for循环在第一个线程到达代码中的该点之前完成,您将始终获得ij具有的 last 值当他们通过for循环时。

有关详细信息,请参阅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);
        ...