与.ContinueWith()的竞争条件

时间:2014-08-04 06:42:52

标签: c# task-parallel-library async-await

我在竞争状态下运行而难以重现,而且从我的代码分析来看,它似乎来自一个不执行的延续(或者直到结束)。

这是一些呈现上下文的伪代码:

Task<Something> GetObject(string id)
{
    // some async code retrieving the object
}

List<Task> tasks = new List<Task>();
List<Something> result = new List<Something>();
foreach (var id in someList)
{
    tasks.Add(GetObject(id).ContinueWith(task => 
    {
        var something = task.Result;
        // do some basic sync stuff on something
        result.Add(something);
    }));
}

await Task.WhenAll(tasks);

随机发生的结果是缺少一些预期的对象。

出于某种原因,我知道foreach进展顺利,并且没有理由不将所有必需的任务添加到List<Task>

仪表或调试尚未给出结果,因为它似乎改善了竞争条件......我希望有人可以在这里发光。谢谢!

2 个答案:

答案 0 :(得分:3)

  

出于某种原因,我知道foreach顺利而且没有   为什么所有必需的任务都不会被添加到   列表。

好吧,也许List不是线程安全的。

如果列表'someList'很短或很长或者可能有重复值,我不知道您的问题,否则请考虑锁定添加和/或an Immutable list

下一个链接有一个很好的解决方案: List<T> thread safety

答案 1 :(得分:2)

列表不是线程安全的。因此,如果2个线程(或更多!)想要向其添加元素,则一切都会发生(根据文档)。实际上,你可能会有一些缺失的附加元素。

如果您想继续使用List,您必须自己进行同步(可能使用锁或更多高级机制)。

您还可以使用其他适合您需要的收藏类型。 System.Collections.Concurrent namespace中的类型保证是线程安全的,但当然比普通的旧List更贵一点。

最后,您的示例似乎根本不需要任何集合操作:Task.WhenAll()返回Task<TResult[]>。您可以这样重写代码:

Something[] result;
result = await Task.WhenAll(somelist.Select(id => GetObject(id));

这更短,更有效,希望更容易阅读。