2012-04-08 101 views
2

我经常应用中的顶级功能的工作原理常见的类似任务异步模式和错误/异常/注销处理

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

的进程*功能:

  • IO绑定(数据库,文件系统,web服务)
  • 可能会抛出异常,只是在调用栈中传播
  • 可能会返回错误的一些非例外呃rors应该停止处理并返回

顶级函数也在后台线程上运行,可以取消。 这意味着全面落实看起来像

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); 
} 

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

我现在的问题是:
所有这些检查早期任务中的失败,错误和取消都变得容易出错,并且不具有可扩展性。 我在这里错过了一些重要的东西,并以错误的方式去做?

+0

你有没有试过这些链接 - 这个领域已经很好地覆盖了,但不知道它是否完全适用于你有什么 - http://nitoprograms.blogspot.com/2010/06/reporting-progress-from-tasks。 HTML - 和http://nitoprograms.blogspot.com/2012/02/reporting-progress-from-async-tasks.html - 我想你应该看看,如果还没有。 – NSGaga 2012-04-08 15:44:46

+0

我假设你不能使用C#5,对吧? – svick 2012-04-08 19:33:20

+0

@svick .Net 3.5和4只。是否等待简化并行操作?我的理解是,等待主要是关于异步工作(即在这种情况下实现Process *方法)。你有一个例子吗? – adrianm 2012-04-09 06:24:53

回答

0

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

+0

但这并非如此。进程3不依赖于2,进程3和4可以同时运行。 – svick 2012-04-08 22:44:01

+0

“如果task1或task2失败或返回错误,则不应运行进程3”。这意味着只有当#1和#2都完成时才能确定是否要启动它。或不? – usr 2012-04-08 22:48:40

+0

据我所知,如果进程2已经完成了一个错误,那么进程3不应该启动,但如果进程2仍在运行,启动它就可以了。至少这是问题中最后一段代码的作用。 – svick 2012-04-08 22:51:33