关于Task.StartNew(Action <object>,Object)方法</object>

时间:2014-03-21 05:18:28

标签: c# .net multithreading task-parallel-library task

我正在这个页面上学习TPL,而且一个代码块让我很困惑。

我正在阅读此页:Task Parallelism (Task Parallel Library)

在一节中,它说下面的代码是正确的解决方案,因为循环中的lambda不能获得每次迭代后变异的值,而是最终值。所以你应该在CustomData对象中包装“i”。代码如下:

class CustomData
{
   public long CreationTime;
   public int Name; 
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data 
      // to the Task constructor. This is useful when you need to capture outer variables 
      // from within a loop. 
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++)
      {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) =>
            {
               CustomData data = obj as CustomData;
               if (data == null) 
                  return;

               data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
               Console.WriteLine("Task #{0} created at {1} on thread #{2}.", data.Name, data.CreationTime, data.ThreadNum);
            },
            new CustomData()
            {
               Name = i,
               CreationTime = DateTime.Now.Ticks
            });
      }
      Task.WaitAll(taskArray);     
   }
}

代码相当简单易懂,但问题出现了:

在Task.Factory.StartNew()方法中,作者使用其重载形式之一:

Task.StartNew(Action<Object>, Object)

我很好奇知道“obj”来自哪里?它如何具有3个属性:Name,CreationTime和ThreadNum。

我在MSDN上搜索时发现没有有用的细节但是:“一个包含动作委托使用的数据的对象。” MSDN它实际上没有解释任何事情。

有人可以解释一下吗?

2 个答案:

答案 0 :(得分:2)

这是一个更简洁的例子,可能有助于解释它。

void StartNew(Action<object> action, object o) {
  action(o);
}

StartNew方法只接受action委托,并通过传递o作为参数来调用它。传递给lambda的值只是在lambda

之后传递给StartNew的值
// Prints "hello world"
StartNew(o => Console.WriteLine(o), "hello world");

如果你概述了传递的值,那么第二个参数是

new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} 

这只是创建一个类型为CustomData的新对象,为它赋予一些属性,并使其成为紧接在它之前定义的lambda的参数。它最终将成为lambda

中的值obj

答案 1 :(得分:2)

这是将不透明状态对象传递给回调的标准模式,它在.NET框架中的许多其他位置使用。一个更简单的例子,SendOrPostCallback

SynchronizationContext.Current.Post(state => 
    MessageBox.Show(state.ToString()), state: "Hello");

将来会调用SendOrPostCallback类型的回调lambda,其中“Hello”为state

state参数可以用作优化,但它不是必需的,既不是在这里也不是Task.Factory.StartNew或在大多数其他情况下提供state参数。

你的lambda是一个闭包,可以访问外部作用域局部变量,所以下面会产生相同的结果,而不会明确地传递state

var message = "Hello";
SynchronizationContext.Current.Post(_ => 
    MessageBox.Show(message), state: null);

同样适用于Task.Factory.StartNew。为此,Task.Factory.StartNew提供了一组accept non-generic action Action而不是Action<object>的覆盖。

因此,您的代码可能看起来像这样,IMO更具可读性:

  for (int i = 0; i < taskArray.Length; i++) {
     var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};

     taskArray[i] = Task.Factory.StartNew(() => 
     {
         if (data == null) 
           return;

         data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
             data.Name, data.CreationTime, data.ThreadNum);
     });
  }