问题描述
我们正在使用StackOverflow中的此代码段来生成一个Task,该Task会在第一个集合的第一个完成任务成功完成.由于其执行具有非线性特性,因此async/await
并不是真正可行的,因此此代码改为使用ContinueWith()
.不过,它没有指定TaskScheduler,而编号 的 资源可能很危险,因为当大多数开发人员通常期望延续性产生TaskScheduler.Default
行为时,它会使用TaskScheduler.Current
.
We are using this code snippet from StackOverflow to produce a Task that completes as soon as the first of a collection of tasks completes successfully. Due to the non-linear nature of its execution, async/await
is not really viable, and so this code uses ContinueWith()
instead. It doesn't specify a TaskScheduler, though, which a number of sources have mentioned can be dangerous because it uses TaskScheduler.Current
when most developers usually expect TaskScheduler.Default
behavior from continuations.
普遍的智慧似乎是,您应该始终将一个明确的TaskScheduler传递给ContinueWith.但是,对于何时使用其他TaskSchedulers最合适,我还没有明确的解释.
The prevailing wisdom appears to be that you should always pass an explicit TaskScheduler into ContinueWith. However, I haven't seen a clear explanation of when different TaskSchedulers would be most appropriate.
与TaskScheduler.Default
相反,最好将TaskScheduler.Current
传递到ContinueWith()
的情况的具体示例是什么?做出此决定时是否要遵循经验法则?
What is a specific example of a case where it would be best to pass TaskScheduler.Current
into ContinueWith()
, as opposed to TaskScheduler.Default
? Are there rules of thumb to follow when making this decision?
对于上下文,这是我指的代码段:
For context, here's the code snippet I'm referring to:
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;
}
推荐答案
可能您需要选择一个适合于正在执行的委托实例执行的操作的任务计划程序.
Probably you need to choose a task scheduler that is appropriate for actions that an executing delegate instance performs.
请考虑以下示例:
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());
}
您的FirstSuccessfulTask()
可能是可以使用TaskScheduler.Default
的示例,因为可以在线程池上安全地执行延续委托实例.
Your FirstSuccessfulTask()
probably is the example where you can use TaskScheduler.Default
, because the continuation delegate instance can be safely executed on a thread pool.
您还可以使用自定义任务计划程序在库中实现自定义计划逻辑.例如,请参阅奥尔良框架网站上的 Scheduler 页.
You can also use custom task scheduler to implement custom scheduling logic in your library. For example see Scheduler page on Orleans framework website.
有关更多信息,请检查:
For more information check:
- 这一切都是关于StephenContexty的 SynchronizationContext
- TaskScheduler,线程和死锁 Cosmin Lazar
- StartNew很危险文章 li>
- It's All About the SynchronizationContext article by Stephen Cleary
- TaskScheduler, threads and deadlocks article by Cosmin Lazar
- StartNew is Dangerous article by Stephen Cleary
这篇关于什么时候应使用TaskScheduler.Current作为参数调用Task.ContinueWith?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!