首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >TaskFactory.CompleteWhenBoth

TaskFactory.CompleteWhenBoth
EN

Code Review用户
提问于 2013-06-19 05:46:06
回答 2查看 124关注 0票数 1

我试图为两个任务构建一些类似于Zip的东西,但我有点担心这段代码中的竞争条件。

代码语言:javascript
复制
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;
}

这是个好办法吗?

代码语言:javascript
复制
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;
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2013-06-19 08:00:58

您不需要自己编写这段代码,已经有一个非常类似的方法:Task.Factory.ContinueWhenAll()

ContinueWhenAll()不直接支持与返回不同类型的Tasks一起工作,但您可以创建一个帮助方法:

代码语言:javascript
复制
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的数组和强制转换:

代码语言:javascript
复制
return factory.ContinueWhenAll(
    new Task[] { t1, t2 }, tasks => f(((Task<T1>)tasks[0]).Result, ((Task<T2>)t2).Result));
代码语言:javascript
复制
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)); });

这段代码有一个竞争条件:如果t1t2同时完成,那么f可能会执行两次。我不认为这是个好主意,因为f可能需要很长时间,或者更糟糕的是,它可能有副作用。

代码语言:javascript
复制
if(Interlocked.Decrement(ref tasksLeft) == 0 && t2.IsCompleted) 

这里的Decrement()避免了前面代码中的争用条件。但这也意味着t2.IsCompleted的检查总是成功的,所以您应该删除它。

但是,您的代码也有一个重要的问题:如果t1t2异常失败,则生成的Task永远不会完成。(在.Net 4.0上,它还会在将来的某个时候使进程崩溃,因为Task中有一个不明显的异常。)

票数 1
EN

Code Review用户

发布于 2013-06-19 06:32:11

你是否有可能使用.NET 4.5?它包含的方法完全可以满足您的需要:Task.WhenAll

票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/27536

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档