何时应该使用TaskScheduler.Current作为参数调用Task.ContinueWith?

Str*_*ior 12 c# multithreading task-parallel-library

我们正在使用StackOverflow中的此代码片段来生成一个任务,该任务在第一个任务集合成功完成后立即完成.由于其执行的非线性特性,async/await实际上并不可行,因此此代码使用ContinueWith().它没有指定的TaskScheduler,虽然,这一个号码 来源都提到可能是危险的,因为它使用TaskScheduler.Current时,大多数开发人员通常希望TaskScheduler.Default从延续的行为.

流行的智慧似乎是你应该总是将一个显式的TaskScheduler传递给ContinueWith.但是,我还没有看到关于何时最合适的TaskScheduler的明确解释.

什么是最好TaskScheduler.Current进入的情况的具体例子ContinueWith(),而不是TaskScheduler.Default?做出这个决定时是否有遵循的经验法则?

对于上下文,这是我所指的代码片段:

public static Task<T> FirstSuccessfulTask<T>(IEnumerable<Task<T>> tasks)
{
    var taskList = tasks.ToList();
    var tcs = new TaskCompletionSource<T>();
    int remainingTasks = taskList.Count;
    foreach(var task in taskList)
    {
        task.ContinueWith(t =>
            if(task.Status == TaskStatus.RanToCompletion)
                tcs.TrySetResult(t.Result));
            else
                if(Interlocked.Decrement(ref remainingTasks) == 0)
                    tcs.SetException(new AggregateException(
                        tasks.SelectMany(t => t.Exception.InnerExceptions));
    }
    return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 9

我不得不咆哮,这让太多的程序员陷入困境.旨在使线程变得容易的每个编程辅助工具都会产生五个程序员无法调试的新问题.

BackgroundWorker是第一个,一个谦虚而明智的尝试隐藏并发症.但是没有人意识到工作者在线程池上运行所以不应该用I/O占用自己.每个人都错了,没有多少人注意到.并且忘记检查RunWorkerCompleted事件中的e.Error,在线程代码中隐藏异常是包装器的普遍问题.

异步/的await模式是最新的,这使得它真的看起来很容易.但它组成非常糟糕,异步乌龟一直向下,直到你到达Main().他们最终必须在C#版本7.2中修复它,因为每个人都被卡住了.但是没有解决库中严重的ConfigureAwait()问题.它完全偏向于图书馆作者知道他们在做什么,值得注意的是,他们中的很多人都在为微软工作并修补WinRT.

Task类弥合了两者之间的差距,其设计目标是使其非常可组合.好的计划,他们无法预测程序员将如何使用它.但也是一个负责任的,鼓励程序员继续使用ContinueWith()将风暴粘在一起.即使这样做没有意义,因为这些任务只是按顺序运行.值得注意的是,他们甚至添加了一个优化,以确保延续在同一个线程上运行,以避免上下文切换开销.好的计划,但创建了这个网站命名的不可攻击的问题.

所以是的,你看到的建议很好.任务对于处理异步性非常有用.当服务进入"云"并且延迟成为您不能再忽视的细节时,您必须处理的常见问题.如果你继续使用那种类型的代码,那么你总是关心执行延续的特定线程.由TaskScheduler提供,它不是FromCurrentSynchronizationContext()提供的低概率.这是async/await发生的原因.


Leo*_*lev 8

您可能需要选择适合执行委托实例执行的操作的任务调度程序。

考虑以下示例:

Task ContinueWithUnknownAction(Task task, Action<Task> actionOfTheUnknownNature)
{
    // We know nothing about what the action do, so we decide to respect environment
    // in which current function is called
    return task.ContinueWith(actionOfTheUnknownNature, TaskScheduler.Current);
}

int count;
Task ContinueWithKnownAction(Task task)
{
    // We fully control a continuation action and we know that it can be safely 
    // executed by thread pool thread.
    return task.ContinueWith(t => Interlocked.Increment(ref count), TaskScheduler.Default);
}

Func<int> cpuHeavyCalculation = () => 0;
Action<Task> printCalculationResultToUI = task => { };
void OnUserAction()
{
    // Assert that SynchronizationContext.Current is not null.
    // We know that continuation will modify an UI, and it can be safely executed 
    // only on an UI thread.
    Task.Run(cpuHeavyCalculation)
        .ContinueWith(printCalculationResultToUI, TaskScheduler.FromCurrentSynchronizationContext());
}
Run Code Online (Sandbox Code Playgroud)

FirstSuccessfulTask()可能是可以使用的示例TaskScheduler.Default,因为可以在线程池上安全地执行延续委托实例。

您还可以使用自定义任务调度程序在您的库中实现自定义调度逻辑。例如,请参阅Orleans 框架网站上的Scheduler页面。

有关更多信息,请检查: