我试图为两个任务构建一些类似于Zip的东西,但我有点担心这段代码中的竞争条件。
public static Task<R> ContinueWhenBoth<T1, T2, R>(this TaskFactory<R> factory, Task<T1> t1,
Task<T2> t2, Func<T1, T2, R> f)
{
var result = new TaskCompletionSource<R>();
t1.ContinueWith(t => { if(t2.IsCompleted) result.TrySetResult(f(t1.Result, t2.Result)); });
t2.ContinueWith(t => { if(t1.IsCompleted) result.TrySetResult(f(t1.Result, t2.Result)); });
return result.Task;
}这是个好办法吗?
public static Task<R> ContinueWhenBoth<T1, T2, R>(this TaskFactory<R> factory, Task<T1> t1,
Task<T2> t2, Func<T1, T2, R> f)
{
var result = new TaskCompletionSource<R>();
int tasksLeft = 2;
t1.ContinueWith(t =>
{
if(Interlocked.Decrement(ref tasksLeft) == 0 && t2.IsCompleted)
result.TrySetResult(f(t1.Result, t2.Result));
});
t2.ContinueWith(t =>
{
if(Interlocked.Decrement(ref tasksLeft) == 0 && t1.IsCompleted)
result.TrySetResult(f(t1.Result, t2.Result));
});
return result.Task;
}发布于 2013-06-19 08:00:58
您不需要自己编写这段代码,已经有一个非常类似的方法:Task.Factory.ContinueWhenAll()。
ContinueWhenAll()不直接支持与返回不同类型的Tasks一起工作,但您可以创建一个帮助方法:
public static Task<R> ContinueWhenBoth<T1, T2, R>(
this TaskFactory<R> factory, Task<T1> t1, Task<T2> t2, Func<T1, T2, R> f)
{
return factory.ContinueWhenAll(new Task[] { t1, t2 }, _ => f(t1.Result, t2.Result));
}如果出于性能原因不想使用闭包,可以使用Tasks的数组和强制转换:
return factory.ContinueWhenAll(
new Task[] { t1, t2 }, tasks => f(((Task<T1>)tasks[0]).Result, ((Task<T2>)t2).Result));t1.ContinueWith(t => { if(t2.IsCompleted) result.TrySetResult(f(t1.Result, t2.Result)); });
t2.ContinueWith(t => { if(t1.IsCompleted) result.TrySetResult(f(t1.Result, t2.Result)); });这段代码有一个竞争条件:如果t1和t2同时完成,那么f可能会执行两次。我不认为这是个好主意,因为f可能需要很长时间,或者更糟糕的是,它可能有副作用。
if(Interlocked.Decrement(ref tasksLeft) == 0 && t2.IsCompleted) 这里的Decrement()避免了前面代码中的争用条件。但这也意味着t2.IsCompleted的检查总是成功的,所以您应该删除它。
但是,您的代码也有一个重要的问题:如果t1或t2异常失败,则生成的Task永远不会完成。(在.Net 4.0上,它还会在将来的某个时候使进程崩溃,因为Task中有一个不明显的异常。)
发布于 2013-06-19 06:32:11
你是否有可能使用.NET 4.5?它包含的方法完全可以满足您的需要:Task.WhenAll。
https://codereview.stackexchange.com/questions/27536
复制相似问题