Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
274 views
in Technique[技术] by (71.8m points)

c# - Why is my custom current scheduler replaced by the default one when I use ConfigureAwait(false)?

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The parameter continueOnCapturedContext in ConfigureAwait(bool continueOnCapturedContext) has the following meaning: If true is specified, this means that the continuation should be marshaled back to the original context captured. If false is specified, the continuation may run on an arbitrary context.

The synchronization context is an abstraction for scheduling. A TaskScheduler is a concrete implementation. So by specifying ConfigureAwait(false), you state that any TaskScheduler can be used. If you want to use your special TaskScheduler, than use ConfigureAwait(true).

For more information on this topic, take a look at this post.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...