问题描述
我编写了一个自定义 TaskScheduler
,它应该在同一线程上执行给定的任务。此任务调度程序与自定义任务工厂一起使用。此任务工厂执行异步方法 ReadFileAsync
,该方法调用 ReadToEndAsync
的另一个异步方法 StreamReader
。
我注意到在使用 ReadToEndAsync().ConfigureAwait(false)
后,当前的任务调度程序恢复为默认的 ThreadPoolTaskScheduler
。如果我删除 ConfigureAwait(false)
,则保留自定义任务调度程序 SameThreadTaskScheduler
。为什么?有没有办法在 ConfigureAwait(false)
执行后将其与相同的自定义调度程序一起使用?
我尝试了多种方法,但结果都是一样的:
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>();
}
}
解决方法
continueOnCapturedContext
中的参数 ConfigureAwait(bool continueOnCapturedContext)
具有以下含义:如果指定了 true
,这意味着应该将延续封送回捕获的原始上下文。如果指定了 false
,则延续可以在任意上下文中运行。
同步上下文是调度的抽象。 TaskScheduler
是一个具体的实现。因此,通过指定 ConfigureAwait(false)
,您声明可以使用任何 TaskScheduler。如果您想使用特殊的 TaskScheduler,请使用 ConfigureAwait(true)
。
有关此主题的更多信息,请查看 this post。