我花了很多时间来理解异步编程原理。但有一点仍不清楚。我对此代码感到困惑:
static async Task Method()
{
Console.WriteLine($"Method entered.");
await Task.Delay(1000);
Console.WriteLine($"Await 1 finished.");
await Task.Delay(1000);
Console.WriteLine($"Await 2 finished");
}
static int Main(string[] args)
{
Console.WriteLine($"Main started.");
return AsyncContext.Run(() => MainAsync(args));
}
static async Task<int> MainAsync(string[] args)
{
var t = Method();
Console.WriteLine("Thread starting sleep.");
Thread.Sleep(10000);
Console.WriteLine("Thread stopped sleeping");
Console.WriteLine(t.IsCompleted ? "Method completed" : "Method not completed");
await t;
return 0;
}
结果:
Main started.
Method entered.
Thread starting sleep.
Thread stopped sleeping
Method not completed
Await 1 finished.
Await 2 finished
据我所知,当主线程处于休眠状态时,应执行来自Method的IO绑定操作(导致Task.Delay模拟IO)并按顺序中断主线程以继续执行方法代码。 所以我希望看到:
Main started.
Method entered.
Thread starting sleep.
Await 1 finished.
Await 2 finished
Thread stopped sleeping
Method completed
我知道Thread.Sleep
我要停止主线程。但据我所知,Method()不应该需要线程,因为它包含IO绑定操作。
任何人都可以解释我误解的地方吗?
我使用的AsynContext是(here)。
答案 0 :(得分:3)
默认情况下,“await”捕获当前synchronization context并在原始上下文中生成延续。 如果上下文未定义,则在默认线程池(TaskScheduler.Default)中生成continuation。
我不熟悉AsyncContext,但它可能在一些同步上下文中生成MainAsync,并且由于Thread.Sleep阻塞占用该上下文的线程,“await”的延续将等到上下文被释放。
这不是一个奇怪的现象,你可以在没有AsyncContext类的情况下重现它。尝试在Windows窗体应用程序中运行相同的代码,您将看到。 Windows窗体具有自己的同步上下文,可防止不同步的控制操作。
要解决此问题,您可以通过使用ConfigureAwait(false)方法告诉“await”不捕获同步上下文。
static async Task Method()
{
Console.WriteLine($"Method entered.");
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine($"Await 1 finished.");
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine($"Await 2 finished");
}
await不会尝试在现有上下文中生成continuation,而是会在线程池任务中生成它。
答案 1 :(得分:1)
为什么您的代码按预期正常运行?
在使用AsyncContext.Run
时,您正在为控制台应用程序提供明确的上下文,否则会NULL Synchronization Context
,当您在MainAsync
中执行以下代码行时:
var t = Method();
Console.WriteLine("Thread starting sleep.");
Thread.Sleep(10000);
然后Method()
开始执行,遇到声明的地方:
await Task.Delay(1000);
它将控制权交还给调用者,在那里你通过使线程休眠10秒Thread.Sleep(10000);
来阻止上下文,所以现在在此睡眠结束之前,在Async方法中不能进行Continuation,因为它等待为了使Continuation上下文可用,当它是空闲的,然后它开始执行Continuation,但到那时它还完成了MainAsync
中的其余语句,这些语句似乎被优先处理并且响应符合预期,它等待着只有在最后,事实上检查任何逻辑(如t.IsCompleted
的任务状态更多的是代码气味,更好的只是await t
,等待任务完成
有两种方法可以获得您期望的行为
ConfigureAwait(false)
,这意味着什么,简单地说,为了运行Async继续,它不需要原始上下文,并且将继续作为真正的异步操作,因此将提供您期望的结果。大多数库都具有异步功能,特别是基于IO的,实现ConfigureAwait(false)
。从Main进行调用return MainAsync(args).Result;
,这将确保控制台应用程序的标准行为,这意味着NULL Synchronization Context
,这意味着Async不关心任何现有Context的延续,即使你正在进行Thread睡眠,它也会在后台进行,因为它不期望上下文,结果会与你期望的相同
Main started.
Method entered.
Thread starting sleep.
Await 1 finished.
Await 2 finished
Thread stopped sleeping
Method completed
0
答案 2 :(得分:0)
AsyncContext计划在单个线程上执行的所有任务。您的方法由Delays和WriteLines组成。您可能会认为Delays类似于IO操作,因为它们不需要执行线程。但是,WriteLine需要一个线程。因此,当Method从Delay唤醒时,它等待线程可用于执行WriteLine。
实际上,即使它不包含WriteLines而且只包含延迟,Method也会阻塞,因为它需要一个线程让Delay返回并启动新的Delay,但如果没有WriteLines则更难注意到。