Does TaskScheduler.Default not always guarantee that a task will run on a pool thread?
Is it always TaskScheduler.Default
not guaranteed to execute a task on a pool thread?
While fixing the error, I found at least one case where it doesn't. It can be reproduced as follows (contrived example 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 other cases?
Also, is there an elegant way to ensure that the action ContinueWith
is executed synchronously if the antecedent task is completed on a pool thread or in a queue on a thread pool queue (I know I can use it QueueUserWorkItem
in ContinueWith
, but I don't like it).
EDITED, I'm guessing I can implement my own TaskScheduler
and check if I'm already on the thread pool thread inside TryExecuteTaskInline
to manage this.
source to share
I think the reason is because you are using TaskContinuationOptions.ExecuteSynchronously
. From doco:
ExecuteSynchronously Indicates that the continuation task should be executed synchronously. By specifying this option, the continuation will run on the same thread that causes the antecedent task to transition to its final state. If the antecedent has already completed when the continuation is created, the continuation will execute on the thread that creates the continuation. Only very short continuation times should be performed synchronously.
If the antecedent task ends on a thread other than the thread pool thread, then the continuation will continue on that thread. So my guess is that there is no scheduling in this case. Another case might be when the continuation task has already completed, which will also run synchronously.
Update
To achieve what you are asking in your second part of the question, I think you will need a custom awaiter. See this text 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();
}
}
source to share