当我使用 ConfigureAwait(false) 时,为什么我的自定义当前调度程序被默认调度程序替换?

问题描述

我编写了一个自定义 TaskScheduler,它应该在同一线程上执行给定的任务。此任务调度程序与自定义任务工厂一起使用。此任务工厂执行异步方法 ReadFileAsync,该方法调用 ReadToEndAsync 的另一个异步方法 StreamReader

我注意到在使用 ReadToEndAsync().ConfigureAwait(false) 后,当前的任务调度程序恢复为认的 ThreadPoolTaskScheduler。如果我删除 ConfigureAwait(false),则保留自定义任务调度程序 SameThreadTaskScheduler。为什么?有没有办法在 ConfigureAwait(false) 执行后将其与相同的自定义调度程序一起使用?

我尝试了多种方法,但结果都是一样的:

  • 更改自定义 TaskFactory 的枚举标志
  • 使用自定义同步上下文Posts 同步回调而不是线程池
  • 在任务工厂上执行的任务函数内部更改和恢复同步
  • 在任务工厂上执行的任务函数之外更改和恢复同步
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