缩进lambda和嵌套动作

当使用lambdas时,通常是在TPL上,我会因缩进而迷失…是否有一些最佳实践来格式化? 例如,请使用以下代码:

Task t1 = factory.StartNew(() => { DoSomething(); } .ContinueWith((t2) => { DoSomethingWhenComplete(); }, TaskContinuationOptions.OnlyOnRanToCompletion) ).ContinueWith((t3) => { DoSomethingOnError(); }, TaskContinuationOptions.OnlyOnFaulted); 
  1. 我的“缩进”是否正确?
  2. 在该示例中,我想执行t1,然后如果它完成OK,则执行t2,并在执行t3时执行错误。 但看起来好像t3是t2的延续,而不是来自t1 …我需要在该代码中修复什么才能获得正确的行为? 我想我已经迷失在那个缩进或遗漏了一些括号……

格式化这个有一些最佳实践吗?

我不知道。 我的格式看起来不错(除了下面的注释)。 或者,您可以只按照Visual Studio自动格式化(尝试从编辑器/高级菜单格式化文档)。

在那个例子中,我想要执行t1然后如果完成ok执行t2并且在执行t3时执行错误。 但看起来t3是从t2继续,而不是从t1 …我需要修复该代码以纠正行为? 我想我已经迷失在那个缩进或遗漏了一些括号……

您问题中的代码片段甚至无法编译。 你可能想要这个:

 Task t1 = factory.StartNew(() => { DoSomething(); }); t1.ContinueWith((t2) => { DoSomethingWhenComplete(); }, TaskContinuationOptions.OnlyOnRanToCompletion); t1.ContinueWith((t2) => { DoSomethingOnError(); }, TaskContinuationOptions.OnlyOnFaulted); 

这可能OnlyOnCanceled ,但你错过了另一个状态: 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/awaitMicrosoft.Bcl.Async而不是ContinueWith 。 代码更容易,更易读,并且可以通过try/catch为您提供结构化的error handling。

如果你不能使用async / await,可以考虑使用Task.Then模式由Stephen Toub进行任务链接( 这里有更多细节)。

  1. 我不确定自己是缩进的最佳方法。 如果我在代码中写一个等效的代码,意义上的变化很小,我可能会得到类似下面的内容。 对不起,这不是我自己解决的问题:

     Task t1 = Task.Factory.StartNew( () => { DoSomething(); }) .ContinueWith( (t2) => { DoSomethingWhenComplete(); }, TaskContinuationOptions.OnlyOnRanToCompletion) .ContinueWith( (t3) => { DoSomethingOnError(); }, TaskContinuationOptions.OnlyOnFaulted); 

    我使用的缩进背后的原因是表达式的“子”/“部分”应该比其“父”/“容器”开始的行缩进得更深。 在前面的行中,方法的参数是方法调用的一部分。 因此,如果方法调用本身处于缩进的一个级别,则参数应该进一步缩进一级:

     MethodCall( arg1, arg2); 

    同样,二进制运算符的两边,例如作用域解析/成员访问( . )都是表达式的子代,我们可以在某种程度上将它们视为处于同一级别。 例如,可能有abca + b + c ,我会认为每个元素都是整个表达式的子元素。 因此,由于每个.ContinueWith()都是在第一行开始的整个语句的一部分,因此它们也应该像下面的多行算术表达式一样缩进:

     var x = 1 + 2 + SomethingComplicated(); 
  2. 任务并行库的一个重点是使您能够继续编写“正常”代码。 你所做的是写了很多代码并调用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(); }); 

    我通过调用DoSomethingOnError()并立即返回来处理catch{}的错误的方式模拟了faultedTask.ContinueWith(action, TaskContinuationOptions.OnlyOnFaulted)行为方式。 该表达式的结果是表示延续Task 。 延续的Task只有在延续本身出错时才会出错。 因此,通过将延续分配给t1而不是原始Task ,您实际上正在捕获并吞下exception,就像我在try{}catch{}捕获并吞下它一样。 只是我写的lambda比尝试手动编写一堆延续更加清楚。

    正如@Noseratio所说 ,如果您使用async / await ,那么您可以获得更清晰的代码。 如你的问题所示,为了卸载一些标准的,非异步的方法调用线程池,转移到async / await实际上对你有帮助并不明显。 但用一个lambda替换一堆TPL API调用似乎是一个值得重构的。