foreach

时间:2016-04-13 10:18:02

标签: c# multithreading thread-safety

我写了一个代码块,但我不确定它是否是线程安全的。

List<Task> tasks = new List<Task>();
foreach (KeyValuePair<string, string> kvp in result)
{
    var t = new Task(async () => 
    {
        int retries = 0;
        bool success = false;
        try
        {
            while (retries <= _maxRetries && !success)
            {
                await doSomething(kvp.Value);
                success = true;
            }
        }
        catch (Exception e)
        {
            retries++;
        }

        if (retries == _maxRetries)
        {
            //TODO: need to do smth about it
        }
    });
    tasks.Add(t);
    t.Start();
}
await Task.WhenAll(tasks);

我可以依赖于编译器设置任务时使用安全的事实 value,意思是只要Im在循环中且任务尚未陈述,设置的值是否正确?

因为,我认为在第一次重试while循环之后,kvp对象将不会像第一次运行任务时那样。

如果它实际上不是线程安全的,我认为它确实不是,它怎么能修复?

2 个答案:

答案 0 :(得分:3)

如果您使用的是C#5,那么您的代码就可以了; foreach的语义已被更改,因此循环变量在逻辑上限定为每次迭代。每Eric Lippert

  

在C#5中,foreach的循环变量将在逻辑上位于循环内部,因此闭包每次都会关闭变量的新副本。

如果您使用的是C#4或更早版本,那么您应该将kvp复制到关闭的本地变量。

您的代码有一个无关的错误:通过Task构造函数初始化的任务不会等待作为参数传递给它的异步函数委托的完成。相反,您应该将Task.Run用于此类代表。

有一种更简单(安全)的方法来实现这一目标:

var tasks = result.Select(kvp => Task.Run(async () =>
{
    int retries = 0;
    bool success = false;
    // ...
})).ToList();

await Task.WhenAll(tasks);

答案 1 :(得分:1)

我想你必须在你的任务中将kvp保存在局部变量中,如下所示:

 var t = new Task(async () => 
    {
        var localKvp = kvp;
        ....
    }

这样它将捕获每个任务的局部变量