任务.ContinueWith回调线程

4
我试图找到这个问题的答案,但是没有找到。我的疑问是,Task.ContinueWith委托在哪个线程上被调用。 就"await"而言,我知道它尝试在捕获的SynchronizationContext上运行,但是对于ContinueWith没有记录任何内容。
我还尝试了一个示例程序,虽然它似乎在Threadpool线程上被调用,但我怀疑在某些情况下它可能会在SynchronizationContext上调用。也许有人可以提供一个明确的答案。

如果您想要默认的await行为,请参见Task.ContinueWith Method(Action<Task>, CancellationToken, TaskContinuationOptions, TaskScheduler),作为最后一个参数传递 TaskScheduler.FromCurrentSynchronizationContext - Ivan Stoev
@HarshMaurya: 你为什么要使用 ContinueWith 呢?你几乎肯定应该使用 await 而不是 ContinueWith - Stephen Cleary
@StephenCleary 这是因为我正在使用C# 4.0。 - Samarsh
2个回答

5
这取决于与继续相关的调度器。默认情况下,任务继续通过 Current 调度程序调度,即与当前执行任务关联的 TaskScheduler。当 ContinueWith 不是在一个任务内调用时,Current 将返回 Default 调度程序,它是 .NET Framework 提供的默认 TaskScheduler 实例,并将在线程池上安排任务。
如果您想影响这种行为,可以调用带有 TaskScheduler 参数的 ContinueWith 重载之一。一个常见的模式是在 UI 线程上创建连续体时传递 TaskScheduler.FromCurrentSynchronizationContext(),因为这会导致在执行时连续体被分派回到 UI 线程。 编辑: 回复 您的评论: 如果从运行在 UI 线程上的连续体中生成一个子任务(旨在在线程池上运行),则可能会出现死锁。在这种情况下,子任务将继承自父任务的任务调度程序,该调度程序将绑定到 UI 线程,导致子任务也在 UI 线程上运行。
Task.Factory.StartNew(() =>
{
    // Do background work.
}).ContinueWith(_ =>
{
    // Update UI, then spawn child task to do more background work...
    Task.Factory.StartNew(() =>
    {
        // ...but child task runs on UI thread!
    });
},
    CancellationToken.None,
    TaskContinuationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext());

为了解决这个问题,您可以使用接受TaskScheduler参数的StartNew重载函数创建子任务,并将TaskScheduler.Default作为参数传递进去:
    // Update UI, then spawn child task to do more background work...
    Task.Factory.StartNew(() =>
    {
        // ...and child task now runs on the thread pool.
    },
        CancellationToken.None,
        TaskCreationOptions.None,
        TaskScheduler.Default);

我正在使用上述代码,但我的解决方案不适用于WPF/WinForms。我该如何调用已分派给给定TaskScheduler的任务? - Sellorio

2

Task.ContinueWith默认使用TaskScheduler.Current进行调度,除非在可选的重载方法中另有指定。

如果在TaskScheduler.Current中没有自定义计划程序(这非常可能),那么您的继续操作将在ThreadPool上运行。

Task.ContinueWith永远不会使用SynchronizationContext,除非您使用TaskScheduler.FromCurrentSynchronizationContext从其创建TaskScheduler

您可以始终明确声明需要哪个TaskScheduler,使用提供的可用重载之一:

task.ContinueWith(
    _ => {}, 
    null, 
    CancellationToken.None, 
    TaskContinuationOptions.None, 
    TaskScheduler.Default); // Scheduled to the ThreadPool

有没有任何情况下TaskScheduler.Current使用SynchronizationContext? 我的代码中出现了死锁,如果我将TaskScheduler.FromCurrentSynchronizationContext作为参数传递,我可以轻松地复制它。 但奇怪的是,有时候即使我不传递任何参数也会发生这种情况,这就是我提出问题的原因。 - Samarsh
1
@HarshMaurya 当然可以,但前提是你已经把它放在那里了。 - i3arnon
@HarshMaurya:你是否从UI continuation中生成子任务?如果是这种情况,你可能会受到我在更新答案中描述的问题的影响。 - Douglas

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