当使用lambdas时,通常是在TPL上,我会因缩进而迷失...是否有一些最佳实践来格式化?例如,请使用以下代码:
Task t1 = factory.StartNew(() =>
{
DoSomething();
}
.ContinueWith((t2) =>
{
DoSomethingWhenComplete();
}, TaskContinuationOptions.OnlyOnRanToCompletion)
).ContinueWith((t3) =>
{
DoSomethingOnError();
}, TaskContinuationOptions.OnlyOnFaulted);
答案 0 :(得分:6)
有一些最佳做法可以格式化吗?
我不知道。我的格式看起来不错(除了下面的注释)。或者,您可以只按照Visual Studio自动格式化(尝试从编辑器/高级菜单格式化文档)。
在那个例子中,我想要执行t1然后如果完成就执行t2然后执行 错误执行t3。但看起来t3是从t2延续而不是从t2延续 t1 ...我需要修复该代码以纠正行为?我想我是 在那个缩进上丢失或遗漏了一些括号...
您问题中的代码片段甚至无法编译。你可能想要这个:
Task t1 = factory.StartNew(() =>
{
DoSomething();
});
t1.ContinueWith((t2) =>
{
DoSomethingWhenComplete();
}, TaskContinuationOptions.OnlyOnRanToCompletion);
t1.ContinueWith((t2) =>
{
DoSomethingOnError();
}, TaskContinuationOptions.OnlyOnFaulted);
这可能会奏效,但您错过了另一个州:OnlyOnCanceled
。我宁愿在同一个地方处理t1
的所有完成状态:
Task t1 = factory.StartNew(() =>
{
DoSomething();
}).ContinueWith((t2) =>
{
if (t2.IsCanceled)
DoSomethingWhenCancelled();
else if (t2.IsFaulted)
DoSomethingOnError(t1.Exception);
else
DoSomethingWhenComplete();
});
这仍然可能缺少一件事:您的代码将在没有同步上下文的随机池线程上继续。例如,如果您在UI线程上调用ContinueWith
,则无法访问DoSomething*
方法中的UI。如果不您的期望,请明确指定任务计划程序以继续:
Task t1 = factory.StartNew(() =>
{
DoSomething();
}).
ContinueWith((t2) =>
{
if (t1.IsCanceled)
DoSomethingWhenCancelled();
else if (t1.IsFaulted)
DoSomethingOnError(t1.Exception);
else
DoSomethingWhenComplete();
}, TaskScheduler.FromCurrentSynchronizationContext());
如果您需要定位.NET 4.0但使用VS2012 +作为开发环境,请考虑使用async/await
和Microsoft.Bcl.Async
代替ContinueWith
。编码更容易,更易读,并且会使用try/catch
为您提供结构化错误处理。
如果您不能使用async / await,请考虑使用Stephen Toub的Task.Then模式进行任务链接(更多详细信息here)。
答案 1 :(得分:2)
我不确定自己是缩进的最佳方式。如果我在代码中写一个等效的代码,意义上的变化很小,那么可能最终得到如下内容。对不起,这不是我自己解决的问题:
Task t1 = Task.Factory.StartNew(
() =>
{
DoSomething();
})
.ContinueWith(
(t2) =>
{
DoSomethingWhenComplete();
},
TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith(
(t3) =>
{
DoSomethingOnError();
},
TaskContinuationOptions.OnlyOnFaulted);
我使用的缩进背后的原因是表达式的“子”/“部分”应该比其“父”/“容器”开始的行缩进得更深。在前面的行中,方法的参数是方法调用的一部分。因此,如果方法调用本身处于缩进的一个级别,则参数应该进一步缩进一级:
MethodCall(
arg1,
arg2);
同样,二元运算符的两边,例如范围解析/成员访问(.
)都是表达式的子代,我们可以在某种程度上将它们视为处于同一级别。例如,可能有a.b.c
或a + b + c
,我会认为每个元素都是整个表达式的子元素。因此,由于每个.ContinueWith()
都是在第一行开始的整个语句的一部分,因此它们也应该像下面的多行算术表达式一样缩进:
var x = 1
+ 2
+ SomethingComplicated();
任务并行库的一个重点是让您继续编写“正常”代码。你所做的是写了很多代码并调用TPL来重新实现C#中的一个特性 - try{}catch{}finally{}
块。此外,使用Task.Run(),假设您只是想将操作提升到线程池(但您可以轻松地将其更改为使用自定义TaskFactory
)。
Task t1 = Task.Run(
() =>
{
try
{
DoSomething();
}
catch (Exception ex)
{
DoSomethingOnError(ex);
// Error: do not proceed as usual, but do not mark t1
// as faulted. We did something magical in
// DoSomethingOnError() that addresses the error so
// that it doesn’t need to be reported back to our
// caller.
return;
}
// Completed successfully!
DoSomethingWhenComplete();
});
我通过调用catch{}
并立即返回来处理DoSomethingOnError()
中的错误的方式模拟了faultedTask.ContinueWith(action, TaskContinuationOptions.OnlyOnFaulted)
的行为方式。该表达式的结果是表示 continuation 的Task
。延续的Task
只有在延续本身出错时才会出错。因此,通过将延续分配给t1
而不是原始Task
,您实际上正在捕捉和吞噬异常,就像我在try{}catch{}
中捕获并吞下它一样。只是我写的lambda比尝试手动编写一堆延续更加清楚。
作为@Noseratio says,如果您在有意义的情况下使用async
/ await
,则会获得更清晰的代码。如您的问题所示,为了将一些标准的非异步方法调用卸载到线程池,移动到async
/ await
实际上对您来说并不明显。但用一个lambda替换一堆TPL API调用似乎是一个有价值的重构。