【问题标题】:Task Asynchronous Pattern and error/exception/cancellation handling任务异步模式和错误/异常/取消处理
【发布时间】:2012-04-09 05:10:20
【问题描述】:

我经常有一些顶级函数工作的应用程序

public Result5 ProcessAll() {
    var result1 = Process1();
    var result2 = Process2();
    var result3 = Process3(result1);
    var result4 = Process4(result1, result2);
    return Process5(result1, result2, result3, result4);
}

Process* 函数的常用功能是:

  • IO Bound(数据库、文件系统、Web 服务)
  • 可能会抛出刚刚在调用堆栈中向上传播的异常
  • 对于一些应该停止处理并返回的非异常错误可能会返回错误

顶层函数也在后台线程上运行,可以取消。 这意味着完整的实现看起来像

public Result5 ProcessAll(CancellationToken cancellationToken) {
    Result1 result1 = Process1();

    if (result1 == null)
        return null;
    cancellationToken.ThrowIfCancellationRequested();

    Result2 result2 = Process2();

    if (result2 == null)
        return null;
    cancellationToken.ThrowIfCancellationRequested();

    Result3 result3 = Process3(result1);

    if (result3 == null)
        return null;
    cancellationToken.ThrowIfCancellationRequested();

    Result4 result4 = Process4(result1, result2);

    if (result4 == null)
        return null;
    cancellationToken.ThrowIfCancellationRequested();

    return Process5(result1, result2, result3, result4);
}

现在假设我需要通过尽可能多地并行运行来加快速度。

还假设 Process* 函数实现了任务异步模式并使用 IO 完成端口或类似端口。

我找不到任何好的模式。
如果我忽略错误/异常/取消,它看起来像这样。

public Result5 ProcessAll(CancellationToken cancellationToken) {
    Task<Result1> task1 = Process1Async();
    Task<Result2> task2 = Process2Async();

    Task<Result3> task3 = task1.ContinueWith(_ => Process3Async(task1.Result)).Unwrap();

    Task<Result4> task4 = Task.Factory.ContinueWhenAll(new[] { task1, task2 }, 
                                                       _ => Process4Async(task1.Result, task2.Result)).Unwrap();

    // This will trigger all exceptions captured
    Task.WaitAll(new[] { task1, task2, task3, task4 });

    return Process5(task1.Result, task2.Result, task3.Result, task4.Result);
}

(我知道这可以像同步运行 task4 一样进行优化,WaitAll 不是必需的,但我只是在这里展示一个模式)

如果我现在尝试处理错误和异常,它可能看起来像这样:

public Result ProcessAll(CancellationToken cancellationToken) {
    Task<Result1> task1 = Process1Async();
    Task<Result2> task2 = Process2Async();

    // Process 3 should not run if task1 or task2 failed or returned error
    Task<Result3> task3 = task1.ContinueWith(_ => {
         if (task1.IsFaulted || task1.Result == null)
             return null;
         if (task2.IsFaulted || (task2.IsCompleted && task2.Result == null)
             return null;
         return Process3Async(task1.Result);
    }).Unwrap();

    // Process4 should not start if any of Process1,Process2 or Process3 returned error or throw exception
    Task<Result4> task4 = Task.Factory.ContinueWhenAll(new[] { task1, task2 }, _ => {
                                                       if (task1.Faulted || task1.Result == null)
                                                           return null;
                                                       if (task2.Faulted || task2.Result == null)
                                                           return null;
                                                       if (task3.Faulted || (task3.IsCompleted && task3.Result == null))
                                                           return null;
                                                       return Process4Async(task1.Result, task2.Result)).Unwrap();

    Task.WaitAll(new[] { task1, task2, task3, task4 });
    if (task1.Result == null || 
        task2.Result == null || 
        task3.Result == null || 
        task4.Result == null)
        return null;
    return Process5(task1.Result, task2.Result, task3.Result, task4.Result);
}

现在我需要进行取消检查:-)

我现在的问题是:
所有这些对早期任务中的失败、错误和取消的检查都容易出错,而且可扩展性不强。 我是否在这里遗漏了一些重要的事情并且做错了?

【问题讨论】:

  • 您是否尝试过这些链接 - 已经很好地涵盖了该领域,但不确定它是否完全适用于您所拥有的 - nitoprograms.blogspot.com/2010/06/… - 和 nitoprograms.blogspot.com/2012/02/… - 我认为你应该看看是否还没有。
  • 我假设你不能使用 C#5,对吧?
  • @svick .Net 3.5 和 4。 await 是否简化了并行运行的事情?我的理解是 await 主要是关于异步工作(即在这种情况下实现 Process* 方法)。你有例子吗?

标签: c# exception-handling task-parallel-library


【解决方案1】:

我只能在所有较早的进程都完成后才可以启动进程 3、4 和 5,但无论如何,它们都没有并行性。所以你不需要为他们使用任务。您只需要使用前两个任务,这会使问题变得更容易。如果您选择将它们作为任务启动,这些任务将始终等待前一个任务,从而消除任何并行性。

【讨论】:

  • 但这里不是这样。进程3不依赖于进程2,进程3和进程4可以同时运行。
  • “如果 task1 或 task2 失败或返回错误,则不应运行进程 3”。这意味着您只能在 #1 和 #2 都已完成的情况下判断是否要启动它。还是不行?
  • 据我了解,如果进程 2 已经完成并出现错误,则不应启动进程 3,但如果进程 2 仍在运行,则可以启动它。至少这就是问题中最后一段代码的作用。
  • 示例代码正在等待#1,并且有一个错误,即它应该等待#2,但没有 - 它有一个竞争条件。它正在检查#2 的结果而不等待结果可用。
  • Svick 是正确的。我正在尝试尽可能多地并行运行。同时我不想开始更多的工作如果我知道之前的任务失败了,不管新任务是否依赖它们。
猜你喜欢
  • 2017-09-26
  • 2011-04-11
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 1970-01-01
  • 2013-02-13
  • 2019-08-20
相关资源
最近更新 更多