缩进lambda和嵌套动作

时间:2014-01-30 19:26:23

标签: c# lambda coding-style task-parallel-library

当使用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 ...我需要在该代码中修复什么才能获得正确的行为?我想我已经迷失在那个缩进或遗漏了一些括号......

2 个答案:

答案 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/awaitMicrosoft.Bcl.Async代替ContinueWith。编码更容易,更易读,并且会使用try/catch为您提供结构化错误处理。

如果您不能使用async / await,请考虑使用Stephen Toub的Task.Then模式进行任务链接(更多详细信息here)。

答案 1 :(得分:2)

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

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

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

    MethodCall(
        arg1,
        arg2);
    

    同样,二元运算符的两边,例如范围解析/成员访问(.)都是表达式的子代,我们可以在某种程度上将它们视为处于同一级别。例如,可能有a.b.ca + 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();
        });
    

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

    作为@Noseratio says,如果您在有意义的情况下使用async / await,则会获得更清晰的代码。如您的问题所示,为了将一些标准的非异步方法调用卸载到线程池,移动到async / await实际上对您来说并不明显。但用一个lambda替换一堆TPL API调用似乎是一个有价值的重构。

相关问题