使用(不滥用)ContinueWith

时间:2015-08-19 09:07:32

标签: c# asynchronous task-parallel-library

假设我们有2个工作函数:

void Step1(); // Maybe long.
void Step2(); // Might be short clean up of step 1.

我经常看到:

Task.Run(() => Step1()).ContinueWith(t => Step2());

创建系列中运行的 2个任务。 当:

Task.Run(() => { Step1(); Step2(); });

创建单一任务,运行系列中的2个功能,可能看起来是SIMPLER的选择。

是否有常识性指南可用于确定何时对更简单的方法采取延期的要求?
上面的例子没有异常处理 - 异常处理会对这些指南产生什么影响呢?

6 个答案:

答案 0 :(得分:6)

  

是否有可用于确定的常识指南   什么时候对更简单的方法需要延续?

ContinueWith使您能够通过TaskContinutationOptions仅在某些条件下调用Step2,例如OnlyOnCanceled OnlyOnFaultedOnlyOnRanToCompletion , 和更多。这样,您就可以构建适合每种情况的工作流程。

您也可以使用一个Task.Run和一个try-catch执行此操作,但这可能更适合您维护。

就我个人而言,我试图避免使用ContinueWith,因为我发现async-await更简洁,更像是同步。我希望在awaittry-catch

答案 1 :(得分:3)

我认为有两个主要原因:

  1. 组合物
  2. ContinueWith方法允许您轻松编写许多不同的任务,并使用辅助方法来构建“延续树”。将此更改为命令性调用限制了这一点 - 它仍然可行,但任务比命令式代码更易于组合。

    1. 错误处理
    2. ContinueWith案例中,即使Step2抛出,Step1也会一直运行。当然,这可以使用try子句进行模拟,但这有点棘手。最重要的是,它不构成,并且它不能很好地扩展 - 如果你发现你必须运行多个步骤,每个步骤都有自己的错误处理,那么你将很难与进行大量的 try-catch ES。当然,Task并不是解决这个问题的唯一方法,也不一定是最好的 - 错误monad也可以让你轻松地构成相互依赖的操作。

答案 2 :(得分:3)

通常,使用最少量的延续。它们使代码和性价比变得混乱。

执行此操作的一个原因是异常行为。即使第一个任务失败,继续也会运行。在这里,据我所知,没有错误行为。在这段特殊的代码中,这似乎不是问题。你会以某种方式需要处理来自Spyder的异常。

通常,人们都在想“我有一条管道!”并将管道分解为步骤。这是很自然的想法。但是,管道步骤不一定需要以延续的形式表现出来。它们只能是有序的方法调用。

答案 3 :(得分:1)

即使第一个任务产生异常(检查ContinueWith),

t.Exception也会运行。

ContinueWith语句中,finally可视为异步try..catch。这正是使它有用的原因。你也可以在调用ContinueWith时细粒度。

答案 4 :(得分:0)

  

是否有可用于确定的常识指南   什么时候对更简单的方法需要延续?

您最有可能使用ContinueWith当您:

  • 需要使用 TPL (例如,您使用返回Task的第三方库中的方法)
  • 可能需要使用回调链
  • 可能只需要为那些以某些结果执行完成的任务运行一些回调方法(例如,错误的任务)

在以下情况下,您可能会使用更简单的方法:

  • 您使用的方法不会返回任务
  • 您只想在一个线程上同步调用两个或多个方法

答案 5 :(得分:0)

首先,我假设你需要做异步工作:

public async void Step1(){ /* bla*/ }

public async void Step2(){ /* bla*/ }

然后打电话应该是:

public async void taskRunner(){
    await Task.Run(() => { await Step1(); await Step2(); }); 
} 

在第二位确保它将像Continue with一样工作,你必须添加异常处理(即使第一个任务以异常结束,也始终执行第二个任务。

public async void taskRunner(){
    await Task.Run(() => { 
        try{
            await Step1(); 
        }catch(Exception e){

        }
        await Step2(); 
    }); 
} 

另一点是任务可以是lambdas,因此他们可以只获取先前的任务结果并将其解压缩到下一个任务中,如果没有ContinueWith则需要额外的变量:

 double[] nums = { 3,5,2,6,5,4,3 };
 await Task.Run(() => { //still missing exception handling here ;)
        double result = await GetSum( nums); 
        await SubtractValue(nums, result); 
    }); 

使用ContinueWith

 double[] nums = { 3,5,2,6,5,4,3 };
 await Task.Run( () => await GetSum(nums))
     .ContinueWith( t => await SubtractValue(nums, t.Result));

在这种特殊情况下,没有语法增益,但是如果没有ContinueWith的帮助,可能会有更复杂的例子,这是不可行的:

另外值得注意的是,编译器足够智能,可以在两种不同的情况下优化代码,因此您必须选择(除非您需要特定的行为),这对您来说更清晰。

我希望我没有犯错,我没有检查代码是否编译,对不起,但是迟到了,明天我会更正答案,以防万一。