我正在努力学习如何使用TPL。我已经对这个主题进行了相当多的阅读,但是我无法理解为什么下面的代码示例会中断,而后面的代码会有效?
我有一个单元测试运行来计算写入的记录,并且还读取输出的文件以进行双重检查。
出现故障:
var tasks = new List<Task>();
Parallel.ForEach(File.ReadLines(featuresLocation), s =>
{
var feature = CreateFeature(s);
if (feature.HasValue)
{
tasks.Add(Task.Factory.StartNew(() =>
{
lock (_locker)
{
featuresWriter.WriteLine(feature.Value);
RecordsWrote++;
}
}));
}
});
Task.WaitAll(tasks.ToArray()); // Breaks
工作:
var tasks = new List<Task>();
Parallel.ForEach(File.ReadLines(featuresLocation), s =>
{
var feature = CreateFeature(s);
if (feature.HasValue)
{
tasks.Add(Task.Factory.StartNew(() =>
{
lock (_locker)
{
featuresWriter.WriteLine(feature.Value);
RecordsWrote++;
}
}));
}
});
Task.WaitAll(tasks.Where(x => x != null).ToArray()); // Works
答案 0 :(得分:4)
var tasks = new List<Task>();
Parallel.ForEach(
{
tasks.Add(...);
});
List<Task> tasks
的使用不是线程安全的。您看到null
元素不应该存在,但运行时间稍长,您也可能会看到其他症状和例外情况。行为未定义。
保护对tasks
的访问权限,将其替换为ConcurrentBag
,或者我的选择完全删除这些任务。您从Parallel.ForEach()
得到了足够的并行性。
答案 1 :(得分:2)
您正在看到一个跨线程问题。
此代码块访问非线程安全List<T>
,这可能导致不可预测且无确定性错误:
tasks.Add(Task.Factory.StartNew(() =>
{
...
}));
您需要锁定任务。添加电话:
lock(tasks)
{
tasks.Add(Task.Factory.StartNew(() =>
{
...
}));
}