当使用 TaskFactory 在 for 循环中启动新任务时,我将当前循环索引作为参数传递给启动任务的 lambda 函数。该索引用于从 List 中选择一个项目并为该项目调用工作函数。看来您不能依赖可靠地传递给工作任务的索引值。
如果您执行控制台输出下方的代码,则会发现一些工作人员没有启动,并且匹配数量的其他工作人员至少启动了两次。它可能看起来像这样:
Results:
Started: 7, Completed: 7, Already Started: 2
这可以通过获取循环索引值的副本并将其传递给工作函数(它是在 Manager RunWorkers 函数中注释掉的位)来“治愈”,这给出了预期的结果:
Results:
Started: 10, Completed: 10, Already Started: 0
我希望了解这种行为,以便我可以适当地防范它(我假设我做了一些愚蠢的事情并且“修复”只会隐藏问题并且不能依赖)
顺便说一句 - 删除 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();
}
}
}