简体   繁体   中英

Task.ContinueWith callback thread

I tried to find an answer for this but couldn't. What I was wondering is that on which thread Task.ContinueWith delegate is called. For await I know that it tries to run it on the captured SynchronizationContext but there is nothing documented for ContinueWith .

I also tried with a sample program and though it seems it is called on Threadpool thread, I suspect that in some scenario it might call on SynchronizationContext . Maybe someone can provide a definitive answer.

This depends on the scheduler that is associated with the continuation. By default, task continuations are scheduled through the Current scheduler, being the TaskScheduler associated with the currently executing task. When ContinueWith is not called from within a task, Current will return the Default scheduler, which is the default TaskScheduler instance provided by the .NET Framework, and which will schedule your tasks on the thread pool.

If you want to influence this behaviour, you can call one of the ContinueWith overloads that takes a TaskScheduler parameter. A common pattern is to pass TaskScheduler.FromCurrentSynchronizationContext() when creating continuations on the UI thread, as this would cause the continuation to be dispatched back onto the UI thread when executed.

Edit : In reply to your comment : The deadlock may arise if you spawn a child task (intended to run on the thread pool) from a continuation running on the UI thread. In such cases, the child task will inherit the task scheduler from the parent task, which would be bound to the UI thread, causing the child task to run on the UI thread too.

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());

To resolve this, you can use the StartNew overload that accepts a TaskScheduler parameter for the child task, and pass TaskScheduler.Default to it:

    // 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);

Task.ContinueWith is scheduled on the TaskScheduler.Current unless specified otherwise by the parameters in one of the optional overloads.

If you don't have a custom scheduler in TaskScheduler.Current (which is very likely) your continuation will run on the ThreadPool .

Task.ContinueWith never uses the SynchronizationContext unless you create a TaskScheduler out of it with TaskScheduler.FromCurrentSynchronizationContext .

You can always state explicitly which TaskScheduler is needed using one of the available overloads:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM