是否有理由更喜欢这些实现中的一个而不是另一个
Is there a reason to prefer one of these implementations over the other
在他的 PluralSight 课程 Asynchronous 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.");
}
}
在 this question 中,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 更复杂的实现妥善解决
第二个解决方案存在二次时间复杂度问题。循环运行 N 次,每次 WhenAny
调用都会为这些任务添加 N 个延续。不要使用该代码,除非您确定任务的数量非常少。
Remove
调用也会导致二次时间复杂度。
另外,第二段代码是阻塞的。您只能在任务完成时取回任务。 InCompletionOrder
立即为您提供这些任务,稍后完成。
我会将 InCompletionOrder
视为一种库方法。将其放入实用程序文件中,不会给您带来维护问题。它的行为 will/can 永远不会改变。我在这里不认为代码大小是个问题。
在他的 PluralSight 课程 Asynchronous 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.");
}
}
在 this question 中,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 更复杂的实现妥善解决
第二个解决方案存在二次时间复杂度问题。循环运行 N 次,每次 WhenAny
调用都会为这些任务添加 N 个延续。不要使用该代码,除非您确定任务的数量非常少。
Remove
调用也会导致二次时间复杂度。
另外,第二段代码是阻塞的。您只能在任务完成时取回任务。 InCompletionOrder
立即为您提供这些任务,稍后完成。
我会将 InCompletionOrder
视为一种库方法。将其放入实用程序文件中,不会给您带来维护问题。它的行为 will/can 永远不会改变。我在这里不认为代码大小是个问题。