我编写了一个代码块,但我不确定它是否是线程安全的。
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,意思是只要我在循环中并且任务还没有声明,设置的值就可以了?
因为,我认为在第一次重试 while 循环之后,kvp
对象不会像任务第一次运行时那样。
如果它实际上不是线程安全的(我认为它确实不是),那么如何修复它?
最佳答案
如果您使用的是 C# 5,那么您的代码就没有问题; foreach
的语义已更改,以便循环变量在逻辑上作用于每次迭代。每Eric Lippert :
In C# 5, the loop variable of a foreach will be logically inside the loop, and therefore closures will close over a fresh copy of the variable each time.
如果您使用的是 C# 4 或更早版本,那么您应该将 kvp
复制到闭包的局部变量。
您的代码有一个不相关的错误:通过 Task
构造函数初始化的任务不会等待作为参数传递给它的异步函数委托(delegate)的完成。相反,您应该对此类委托(delegate)使用 Task.Run
。
有一种更简单(安全)的方法可以实现此目的:
var tasks = result.Select(kvp => Task.Run(async () =>
{
int retries = 0;
bool success = false;
// ...
})).ToList();
await Task.WhenAll(tasks);
关于C# foreach 内的线程安全闭包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36595378/