我们正在使用StackOverflow中的this code snippet来生成一个Task,该任务将在第一个任务集成功完成后立即完成。由于其执行的非线性性质,async/await
并不切实可行,因此此代码改用ContinueWith()
。不过,它没有指定TaskScheduler,number of sources提到的这可能很危险,因为大多数开发人员通常期望TaskScheduler.Current
时使用TaskScheduler.Default
延续的行为。
普遍的智慧似乎是,您应该始终将一个明确的TaskScheduler传递给ContinueWith。但是,对于哪种TaskSchedulers最合适的时间,我还没有明确的解释。
与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;
}
答案 0 :(得分:9)
我必须大声疾呼,这使太多的程序员陷入困境。每一种旨在使线程看起来简单的编程辅助工具都会产生五个新问题,程序员无法调试。
BackgroundWorker是第一个,它是掩盖并发症的适度而明智的尝试。但是没有人意识到工作线程是在线程池上运行的,因此永远不要占用I / O。每个人都会犯错,很少有人注意到。忘记在RunWorkerCompleted事件中检查e.Error,在线程代码中隐藏异常是包装程序的普遍问题。
异步/等待模式是最新的,它使真的看起来很容易。但是它的组成异常差,一直到异步乌龟,直到到达Main()为止。他们最终不得不在C#版本7.2中修复此问题,因为每个人都对此卡住了。但是不能解决库中严重的ConfigureAwait()问题。它完全偏向于图书馆作者知道他们在做什么,值得注意的是,他们中的许多人都为Microsoft工作,并使用WinRT进行修改。
Task类弥合了两者之间的鸿沟,其设计目标是使其非常易组合。好的计划,他们无法预测程序员将如何使用它。但是这也是一个责任,激发了程序员ContinueContin()掀起一场将任务粘合在一起的风暴。即使这样做没有意义,因为这些任务只是按顺序运行。值得注意的是,他们甚至添加了优化功能,以确保延续在同一线程上运行,以避免上下文切换开销。好的计划,但是却造成了这个网站的命名无法克服的问题。
是的,您看到的建议是一个很好的建议。任务对于处理异步性非常有用。当服务转移到“云”中时,您必须处理的一个常见问题是延迟,而细节成为您不再可以忽略的细节。如果您使用ContinuousWith()这样的代码,那么您总是会关心执行延续的特定线程。由TaskScheduler提供,它不是FromCurrentSynchronizationContext()提供的可能性很小。异步/等待是怎么发生的。
答案 1 :(得分:3)
如果当前任务是子任务,则使用TaskScheduler.Current
意味着调度程序将是它所在的任务所调度的;并且如果不在另一个任务中,则TaskScheduler.Current
将是TaskScheduler.Default
并因此使用ThreadPool。
如果使用TaskScheduler.Default
,则它将始终转到ThreadPool。
您将使用TaskScheduler.Current
的唯一原因:
为避免默认的调度程序问题,您应始终传递一个 对
TaskScheduler
和Task.ContinueWith
明确使用Task.Factory.StartNew
。
摘自Stephen Cleary的帖子ContinueWith is Dangerous, Too。
Stephen Toub在他的MSDN blog上有进一步的解释。
答案 2 :(得分:2)
我当然不认为我有能力提供防弹答案,但我会给我5美分。
与TaskScheduler.Default相反,最好将TaskScheduler.Current传递给ContinueWith()的情况的具体示例是什么?
想象一下,您正在使用Web服务器自然使多线程化的Web API。因此,您需要妥协并行性,因为您不想使用Web服务器的所有资源,但是同时您想加快处理时间,因此决定使用较低的并发级别来创建自定义任务调度程序,因为不是。
现在,您的api需要查询一些数据库并排序结果,但是这些结果数以百万计,因此您决定通过Merge Sort(除法和征服)来进行处理,那么您需要使该算法的所有子任务都符合您的自定义任务计划程序(TaskScheduler.Current
),因为否则您将最终占用该算法的所有资源,并且Web服务器线程池将饿死。
何时使用TaskScheduler.Current,TaskScheduler.Default,TaskScheduler.FromCurrentSynchronizationContext()或其他一些TaskScheduler
取自here
的示例private void button_Click(…)
{
… // #1 on the UI thread
Task.Factory.StartNew(() =>
{
… // #2 long-running work, so offloaded to non-UI thread
}).ContinueWith(t =>
{
… // #3 back on the UI thread
}, TaskScheduler.FromCurrentSynchronizationContext());
}
TaskScheduler.FromCurrentSynchronizationContext()
显式传递给TaskFactory
或{{1 }}方法,以后您将使用连续任务或内部任务(非常糟糕的imo)。答案 3 :(得分:2)
可能您需要选择一个适合于正在执行的委托实例执行的操作的任务计划程序。
请考虑以下示例:
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
的示例,因为延续委托实例可以在线程池上安全地执行。
您还可以使用自定义任务计划程序在库中实现自定义计划逻辑。例如,请参见奥尔良框架网站上的Scheduler页。
有关更多信息,请检查: