C# 继续使用(而不是滥用)
假设我们有两个辅助函数:C# 继续使用(而不是滥用),c#,asynchronous,task-parallel-library,C#,Asynchronous,Task Parallel Library,假设我们有两个辅助函数: void Step1(); // Maybe long. void Step2(); // Might be short clean up of step 1. 我经常看到: Task.Run(() => Step1()).ContinueWith(t => Step2()); 它创建了两个在系列中运行的任务。 当: 创建一个单个任务,运行系列中的两个函数,这似乎是一个更简单的选择 是否有常识指南可用于确定在较简单的方法中何时实际需要延续? 上述示例没有
void Step1(); // Maybe long.
void Step2(); // Might be short clean up of step 1.
我经常看到:
Task.Run(() => Step1()).ContinueWith(t => Step2());
它创建了两个在系列中运行的任务。
当:
创建一个单个任务,运行系列中的两个函数,这似乎是一个更简单的选择
是否有常识指南可用于确定在较简单的方法中何时实际需要延续?上述示例没有异常处理-异常处理对这些指导原则的影响程度如何?
ContinueWith
将运行,即使第一个任务产生异常(检查t.exception
)
在
try..catch
语句中,可以将带有的ContinueWith
视为异步finally
。这正是它有用的原因。当调用ContinueWith
时,您还可以细化纹理。有两个主要原因:
构图
ContinueWith
方法允许您轻松组合许多不同的任务,并使用助手方法构建“continuence树”。将此更改为命令式调用限制了这一点-这仍然是可能的,但任务比命令式代码更易于组合
错误处理
在ContinueWith
情况下,Step2
始终运行,即使Step1
抛出。当然,这可以用try
子句来模拟,但这有点棘手。最重要的是,它不能组合,而且不能很好地扩展-如果您发现必须运行多个步骤,每个步骤都有自己的错误处理,那么您将在try-catch
es中遇到很多困难。当然,Task
s不是解决这个问题的唯一方法,也不一定是最好的方法——错误单子也可以让您轻松地组合相互依赖的操作
是否有可用于确定的常识指导原则
在较简单的方法中,实际需要延续时
ContinueWith
使您能够仅在某些情况下通过调用Step2
,例如onlyOnCancelled
OnlyOnFaulted
,OnlyOnRanToCompletion
,等等。这样,您就可以编写适合每种情况的工作流
您也可以通过一个任务来完成此任务。运行并尝试catch
,但这可能需要您进行更多维护
就我个人而言,我试图避免使用ContinueWith
,因为我发现async wait
的冗长程度更低,更像是同步的。我宁愿等待
在尝试捕获
通常,使用最少的持续时间。它们会使代码变得混乱,并降低性能成本
这样做的一个原因是异常行为。即使第一个任务失败,继续操作也将运行。在这里,据我所知没有错误行为。在这段特定的代码中,这似乎不是一个问题。您可能需要从t
处理异常
通常,人们会认为“我有一条管道!”并将管道分解为多个步骤。这是很自然的想法。但是管道步骤不一定需要以延续的形式表现出来。它们只能是按顺序排列的方法调用
是否有可用于确定的常识指导原则
在较简单的方法中,实际需要延续时
当您:
- 需要使用TPL(例如,您使用第三方库中返回任务的方法)
- 可能需要使用回调链
- 可能需要仅对那些完成执行并产生特定结果的任务(例如错误任务)运行某些回调方法
在以下情况下,您可能会使用更简单的方法:
- 您使用的方法不会返回任务
- 您只需要在一个线程上同步调用两个或多个方法
首先,我假设您需要执行异步作业:
public async void Step1(){ /* bla*/ }
public async void Step2(){ /* bla*/ }
那么电话应该是:
public async void taskRunner(){
await Task.Run(() => { await Step1(); await Step2(); });
}
其次,要确保它的工作方式类似于继续执行
,您必须添加异常处理(即使第一个任务以异常结束,也始终执行第二个任务)
public async void taskRunner(){
await Task.Run(() => {
try{
await Step1();
}catch(Exception e){
}
await Step2();
});
}
另一点是,任务可以是lambda,因此它们可以只获取上一个任务结果并将其解压缩到下一个任务中,如果不使用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);
});
使用继续使用
double[] nums = { 3,5,2,6,5,4,3 };
await Task.Run( () => await GetSum(nums))
.ContinueWith( t => await SubtractValue(nums, t.Result));
在这种特殊情况下,没有语法增益,但是如果没有ContinueWith的帮助,可能会有更复杂的示例无法编写:
还值得注意的是,编译器足够聪明,可以在两种不同的情况下优化相同的代码,因此您必须选择(除非您需要特定的行为)哪种方式对您来说更清楚
我希望我没有犯错误,我没有检查代码是否编译,很抱歉,但是在这里太晚了,我明天会更正答案,以防万一。性能可能不是问题-任何使用CPU任务的地方都会出现额外延续带来的开销可以忽略不计的情况。当然,除非您使用的是GUI调度程序rse…当您有两个以上的步骤时,维护try-catch
es特别困难,其中一些步骤在出现故障时应该运行,而另一些步骤不应该运行。这种情况会发生。当然,您可以使用类似错误单子的东西来代替任务等待对于异步I/O的继续是一个很好的选择,但是有时,你仍然需要使用继续操作,例如,在等待操作树时,而不是一个“堆栈”。@Luaan这很难而且不可读。保持这样的多个连续操作会很痛苦。你不必完全手动操作,的一大好处是
double[] nums = { 3,5,2,6,5,4,3 };
await Task.Run( () => await GetSum(nums))
.ContinueWith( t => await SubtractValue(nums, t.Result));