简体   繁体   中英

Does TaskScheduler.Default not always guarantee the task will be executed on a pool thread?

Does TaskScheduler.Default not always guarantee the task will be executed on a pool thread?

While fixing a bug, I found at least one case when it doesn't. It can be reproduced like this (a contrived example made from real code):

var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
sc.Post(_ => tcs.SetResult(true), null);
await tcs.Task.ContinueWith(_ =>
    {
        // breaks here
        Debug.Assert(Thread.CurrentThread.IsThreadPoolThread);
    }, 
    CancellationToken.None, 
    TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

Are there any other cases?

Also, is there an elegant way to make sure the ContinueWith action is executed synchronously, if the antecedent task has completed on a pool thread, or gets queued to thread pool otherwise (I know I can use QueueUserWorkItem in ContinueWith action, but I don't like it).

EDITED, I guess I can implement my own TaskScheduler and check if I am already on a thread pool thread inside TryExecuteTaskInline , to control this.

I think the reason this happens is because you use the TaskContinuationOptions.ExecuteSynchronously . From the doco:

ExecuteSynchronously Specifies that the continuation task should be executed synchronously. With this option specified, the continuation will be run on the same thread that causes the antecedent task to transition into its final state. If the antecedent is already complete when the continuation is created, the continuation will run on the thread creating the continuation. Only very short-running continuations should be executed synchronously.

If the antecedent task completes on a thread other than a thread pool thread then the continuation will run on that thread also. So I guess in that case no scheduling occurs. Another case might be when the continuation task is already complete, which will also run synchronously.

Update

To achieve what you are asking in your second part of the question I think you'll need a custom awaiter. See this articl e for more details but something like this might work for you:

public static class Extensions
{
    public static ThreadPoolTaskAwaiter WithThreadPool(this Task task)
    {
        return new ThreadPoolTaskAwaiter(task);
    }

    public class ThreadPoolTaskAwaiter : INotifyCompletion
    {
        private readonly TaskAwaiter m_awaiter;

        public ThreadPoolTaskAwaiter(Task task)
        {
            if (task == null) throw new ArgumentNullException("task");
            m_awaiter = task.GetAwaiter();
        }

        public ThreadPoolTaskAwaiter GetAwaiter() { return this; }

        public bool IsCompleted { get { return m_awaiter.IsCompleted; } }

        public void OnCompleted(Action continuation)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                continuation();
            }
            else
            {
                Task.Run(continuation);
            }                
        }

        public void GetResult()
        {
            m_awaiter.GetResult();
        }
    }
}

Then you use it like this:

public static async Task Execute()
{          
    await Task.Delay(500).WithThreadPool();

    // does not break here
    if (!Thread.CurrentThread.IsThreadPoolThread)
    {
        Debugger.Break();
    }
}

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