我正在这个页面上学习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它实际上没有解释任何事情。
有人可以解释一下吗?
答案 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);
});
}