C# 当我使用ConfigureWait(false)时,为什么我的自定义当前计划程序被默认计划程序替换?
我编写了一个定制的C# 当我使用ConfigureWait(false)时,为什么我的自定义当前计划程序被默认计划程序替换?,c#,.net,asynchronous,async-await,taskscheduler,C#,.net,Asynchronous,Async Await,Taskscheduler,我编写了一个定制的TaskScheduler,它应该在同一个线程上执行给定的任务。此任务计划程序与自定义任务工厂一起使用。此任务工厂执行一个异步方法ReadFileAsync,该方法调用streamreadreader的另一个异步方法ReadToEndAsync 我注意到,在使用ReadToEndAsync().ConfigureAwait(false)后,当前任务计划程序将恢复为默认任务计划程序ThreadPoolTaskScheduler。如果删除ConfigureAwait(false),
TaskScheduler
,它应该在同一个线程上执行给定的任务。此任务计划程序与自定义任务工厂一起使用。此任务工厂执行一个异步方法ReadFileAsync
,该方法调用streamreadreader
的另一个异步方法ReadToEndAsync
我注意到,在使用ReadToEndAsync().ConfigureAwait(false)
后,当前任务计划程序将恢复为默认任务计划程序ThreadPoolTaskScheduler
。如果删除ConfigureAwait(false)
,将保留自定义任务计划程序samethreadstaskscheduler
。为什么?在执行相同的自定义计划程序后,是否有任何方法可以使用ConfigureAwait(false)
我尝试了多种方法,但结果都是一样的:
- 更改自定义任务工厂的枚举标志
- 使用一个自定义的同步上下文,
回调,而不是线程池同步发布
- 更改并恢复在任务工厂上执行的任务函数内的同步
- 在任务工厂上执行的任务功能之外更改并恢复同步
公共静态类程序
{
私有静态只读字符串DesktopPath=Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
公共静态void Main()
{
_=AsyncHelper.RunSynchronously(ReadFileAsync);
}
私有静态异步任务ReadFileAsync()
{
//打印“SameThreadTaskScheduler”
Console.WriteLine(TaskScheduler.Current.GetType().Name);
使用var fs=File.OpenText(Path.Combine(desktopath,“hello.txt”);
var content=await fs.ReadToEndAsync().ConfigureAwait(false);//1;
受保护的覆盖无效队列任务(任务任务)
{
this.TryExecuteTask(任务);
}
受保护的覆盖bool TryExecuteTaskInline(任务任务,bool任务先前排队)
{
this.TryExecuteTask(任务);
返回true;
}
受保护的重写IEnumerable GetScheduledTasks()
{
返回可枚举的.Empty();
}
}
配置等待(bool continueOnCapturedContext)中的参数continueOnCapturedContext
具有以下含义:如果指定了true
,这意味着应将延续封送回捕获的原始上下文。如果指定了false
,则延续可以在任意上下文上运行
同步上下文是调度的抽象。TaskScheduler
是一个具体的实现。因此,通过指定ConfigureAwait(false)
,可以声明可以使用任何TaskScheduler。如果要使用特殊的TaskScheduler,请使用ConfigureAwait(true)
有关此主题的更多信息,请参阅ConfigureAwait(bool continueOnCapturedContext)中的参数continueOnCapturedContext
具有以下含义:如果指定了true
,这意味着应将延续封送回捕获的原始上下文。如果指定了false
,则延续可以在任意上下文上运行
同步上下文是调度的抽象。TaskScheduler
是一个具体的实现。因此,通过指定ConfigureAwait(false)
,可以声明可以使用任何TaskScheduler。如果要使用特殊的TaskScheduler,请使用ConfigureAwait(true)
有关此主题的更多信息,请参阅。如果您关心保持自定义状态一致,了解调用ConfigureAwait(false)
的原因会有很大帮助。您似乎感到沮丧,因为ConfigureAwait(false)
做的正是应该做的。这有点奇怪。这就像问+
操作符为什么执行加法,以及如何阻止它执行加法。好吧,好吧。如果您不喜欢,则配置等待(false)
配置wait
以使其不捕获当前上下文或调度程序,您希望它做什么?您希望它是一个不可操作的吗?您试图用自定义调度程序实现什么?可以用asynchlocal
来实现吗?您是否在阻止异步代码?我试图理解以及为什么当前自定义任务计划程序在使用ConfigureAwait(false)调用async后发生更改
。通过您的评论和回答,我现在明白了!我的故事是,我正在开发一个桌面.NET 4.5应用程序,该应用程序使用自定义任务调度程序来限制可以生成的线程数。在尝试将其迁移到.NET Core 3.1时,我不得不用HttpClient
替换HttpWebRequest
。起初,我注意到它使用了自定义线程限制器任务调度程序,导致线程不足。这就是为什么我试图确保HttpClient
异步方法和回调在另一个任务调度程序的同一线程上执行。了解调用ConfigureAwait(false)的原因会有很大帮助
如果您关心保持自定义状态的一致性,您可能会感到沮丧,因为配置等待(false)
做的正是应该做的。这有点奇怪。这就像问+
操作符为什么执行加法,以及如何阻止它执行加法。好吧,好吧。如果您不喜欢,则配置等待(false)
配置wait
以使其不捕获当前上下文或调度程序,您希望它做什么?是否希望它成为不可操作?您试图用自定义调度程序实现什么?是否可以用AsyncLocal
来实现?是否阻止
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>();
}
}