这两种实现方式之间是否有优劣之分?

6
在他的PluralSight课程异步C# 5中,Jon Skeet提供了一个方便的扩展方法InCOmpletionOrder的实现:
public static IEnumerable<Task<T>> InCompletionOrder<T>(this IEnumerable<Task<T>> source)
{
    var inputs = source.ToList();
    var boxes  = inputs.Select(x => new TaskCompletionSource<T>()).ToList();
    int currentIndex = -1;

    foreach (var task in inputs)
    {
        task.ContinueWith(completed =>
        {
            var nextBox = boxes[Interlocked.Increment(ref currentIndex)];
            PropagateResult(completed, nextBox);
        }, TaskContinuationOptions.ExecuteSynchronously);
    }

    return boxes.Select(box => box.Task);
}

private static void PropagateResult<T>(Task<T> completedTask,
      TaskCompletionSource<T> completionSource)
{
    switch(completedTask.Status)
    {
      case TaskStatus.Canceled:
          completionSource.TrySetCanceled();
          break;
      case TaskStatus.Faulted:
          completionSource.TrySetException(completedTask.Exception.InnerExceptions);
          break;
      case TaskStatus.RanToCompletion:
          completionSource.TrySetResult(completedTask.Result);
          break;
      default:
          throw new ArgumentException ("Task was not completed.");
    }
}

这个问题中,Martin Neal提供了一种看起来更加优雅的实现方式,使用yield return
public static IEnumerable<Task<T>> InCompletionOrder<T>(this IEnumerable<Task<T>> source)
{
    var tasks = source.ToList();

    while (tasks.Any())
    {
        var t = Task.WhenAny(tasks);
        yield return t.Result;
        tasks.Remove(t.Result);
    }
}

作为对异步编程还有些陌生的人,能否有人描述一下马丁·尼尔(Martin Neal)的实现中可能出现的具体问题,以及乔恩·斯基特(Jon Skeet)更复杂的实现是如何解决这些问题的。


@MickyD:确切地说,我犹豫了,因为我不想将代码量增加近四倍,除非我至少大致了解为什么要这样做。下面的usr已经给了我两个很好的理由去这样做。 - Pieter Geerkens
同意。我只是对“看似更优雅”有些疑问。在编程的所有方面中,我们不能总是将行数等同于优雅。Servy 在下面提出了一些好观点。 :) - user585968
1个回答

6
第二种解决方案存在二次时间复杂度问题。循环运行N次,每个WhenAny调用都会向这些任务添加N个连续项。除非您确定任务数非常小,否则不要使用该代码。
Remove调用也会导致二次时间复杂度。
此外,第二段代码是阻塞的。只有当任务完成时,您才会收到回复。InCompletionOrder立即给你这些任务,然后它们稍后完成。
我认为InCompletionOrder是一个库方法。将其放入实用程序文件中,它就不会给您带来维护问题。它的行为永远不会改变。我在这里不认为代码大小是个问题。

2
Jon的代码可以正确处理已接受和取消的任务,而另一个片段基本上只有在所有任务都成功完成时才能正常工作。说实话,这一点以及它是阻塞的(基本上使IEnumerable<Task<T>>的返回类型本质上是一个谎言)比性能问题更重要,所以我会先强调这两个点,只将性能作为次要因素提及。 - Servy
1
@Servy 你确定异常的行为吗?第二段代码没有访问它返回的任务的结果。其他的观点我已经注意到了。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接