I wrote a custom TaskScheduler
which is supposed to execute given tasks on the same thread. This task scheduler is used with a custom task factory. This task factory executes an async method ReadFileAsync
which calls another async method ReadToEndAsync
of StreamReader
.
I've noticed that after using ReadToEndAsync().ConfigureAwait(false)
, the current task scheduler is reverted back to the default one, ThreadPoolTaskScheduler
. If I remove ConfigureAwait(false)
, the custom task scheduler SameThreadTaskScheduler
is kept. Why? Is there any way to use ConfigureAwait(false)
with the same custom scheduler after its execution?
I've tried multiple things, but the result is the same:
- Change the enum flags of the custom
TaskFactory
- Using a custom synchronization context that
Posts
the callback synchronously instead of the thread pool
- Change and revert the synchronization inside the task function executed on the task factory
- Change and revert the synchronization outside of the task function executed on the task factory
public static class Program
{
private static readonly string DesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
public static void Main()
{
_ = AsyncHelper.RunSynchronously(ReadFileAsync);
}
private static async Task<string> ReadFileAsync()
{
// Prints "SameThreadTaskScheduler"
Console.WriteLine(TaskScheduler.Current.GetType().Name);
using var fs = File.OpenText(Path.Combine(DesktopPath, "hello.txt"));
var content = await fs.ReadToEndAsync().ConfigureAwait(false); // <-------- HERE
// With ReadToEndAsync().ConfigureAwait(false), prints "ThreadPoolTaskScheduler"
// With ReadToEndAsync() only, prints "SameThreadTaskScheduler"
Console.WriteLine(TaskScheduler.Current.GetType().Name);
return content;
}
}
public static class AsyncHelper
{
private static readonly TaskFactory SameThreadTaskFactory = new TaskFactory(
CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
new SameThreadTaskScheduler());
public static TResult RunSynchronously<TResult>(Func<Task<TResult>> func)
{
var oldContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(null);
return SameThreadTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldContext);
}
}
}
public sealed class SameThreadTaskScheduler : TaskScheduler
{
public override int MaximumConcurrencyLevel => 1;
protected override void QueueTask(Task task)
{
this.TryExecuteTask(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
this.TryExecuteTask(task);
return true;
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
}
question from:
https://stackoverflow.com/questions/65830903/why-is-my-custom-current-scheduler-replaced-by-the-default-one-when-i-use-config 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…