当使用TaskFactory在for循环中启动新任务时,我将当前循环索引作为参数传递给启动Task的lambda函数。 该索引用于从List中选择项目并调用该项目的worker函数。看来你不能依赖索引的值可靠地传递给worker Task。
如果执行下面的代码,控制台输出会显示某些工作人员没有启动,并且至少两次启动了匹配数量的其他工作人员。它可能看起来像这样:
Results:
Started: 7, Completed: 7, Already Started: 2
这可以通过获取循环索引值的副本并将其传递给worker函数(它是在Manager RunWorkers函数中注释掉的位)来“固化”,并且这给出了预期的结果:
Results:
Started: 10, Completed: 10, Already Started: 0
我希望了解这种行为,以便我可以正确地防范它(我假设我做了一些愚蠢的事情,'修复'只隐藏了问题,不能依赖它)
BTW - 删除Manager RunOne函数中的保护可能会导致ArgumentOutOfRange异常,因为索引太大。
我已经为下面的C#consol应用程序(Visual Studio 2013)提供了代码,首先是Worker
namespace ThreadingTest
{
public class Worker
{
public bool hasStarted = false;
public bool hasCompleted = false;
public bool hasAlreadyStarted = false;
public readonly int index;
private double value;
public Worker(int _index)
{
index = _index;
}
public void workSocksOff()
{
if (hasStarted)
{
hasAlreadyStarted = true;
return;
}
hasStarted = true;
// Do real work
for (int i=0; i<10000000; ++i)
{
value = Math.Sqrt(i);
}
hasCompleted = true;
}
}
}
然后是经理
namespace ThreadingTest
{
public class Manager
{
public List<Worker> Workers = new List<Worker>();
private Object taskLock = new Object();
public int TaskCount { get; set; }
public void RunTest()
{
AddWorkers();
RunWorkers();
}
private void RunWorkers()
{
TaskCount = 0;
TaskFactory taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
Task[] taskPool = new Task[Workers.Count];
for (int i=0; i<Workers.Count; ++i)
{
//int why = i;
//taskPool[i] = taskFactory.StartNew(() => this.RunOne(why))
taskPool[i] = taskFactory.StartNew(() => this.RunOne(i))
.ContinueWith( (antecedant) =>
{
lock (taskLock) { TaskCount += 1; }
}
);
}
Task.WaitAll(taskPool);
}
private void RunOne(int index)
{
if (index >= Workers.Count)
return;
Workers[index].workSocksOff();
}
private void AddWorkers()
{
for (var i = 0; i < 10; ++i)
Workers.Add(new Worker(i));
}
}
}
最后程序本身
namespace ThreadingTest
{
class Program
{
static void Main(string[] args)
{
Manager manager = new Manager();
manager.RunTest();
int started = 0, completed = 0, alreadyStarted = 0;
foreach (Worker w in manager.Workers)
{
if (w.hasStarted) started++;
if (w.hasCompleted) completed++;
if (w.hasAlreadyStarted) alreadyStarted++;
}
Console.WriteLine("Results: ");
Console.WriteLine("\tStarted: {0}, Completed: {1}, Already Started: {2}", started, completed, alreadyStarted);
Console.ReadKey();
}
}
}