C# 等待归来后的意外行为
我知道关于async/Wait有很多问题,但我找不到任何答案 我遇到了一些我不理解的事情,考虑下面的代码:C# 等待归来后的意外行为,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我知道关于async/Wait有很多问题,但我找不到任何答案 我遇到了一些我不理解的事情,考虑下面的代码: void Main() { Poetry(); while (true) { Console.WriteLine("Outside, within Main."); Thread.Sleep(200); } } async void Poetry() { //.. stuff happens before await
void Main()
{
Poetry();
while (true)
{
Console.WriteLine("Outside, within Main.");
Thread.Sleep(200);
}
}
async void Poetry()
{
//.. stuff happens before await
await Task.Delay(10);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Inside, after await.");
Thread.Sleep(200);
}
}
void Main()
{
诗歌();
while(true)
{
控制台。写线(“外部,内部主。”);
睡眠(200);
}
}
异步void诗歌()
{
//…事情在等待之前就发生了
等待任务。延迟(10);
对于(int i=0;i<10;i++)
{
控制台。WriteLine(“内部,等待后”);
睡眠(200);
}
}
显然,在await操作符上,当正在等待的方法在后台运行时,控件返回给调用方。(假设IO操作)
但是在控件返回到await操作符之后,执行变得并行,而不是(我的期望)保持单线程
我希望在“延迟”完成后,线程将被迫回到诗歌方法中,从它离开的地方继续
确实如此。对我来说奇怪的是,为什么“Main”方法一直在运行?这是一根线从一根跳到另一根吗?还是有两条平行的线
这不是一个线程安全问题吗
我觉得这令人困惑。我不是专家。谢谢。当您调用异步例程时,其目的是允许程序运行方法,同时仍允许调用例程、窗体或应用程序继续响应用户输入(换句话说,继续正常执行)。“await”关键字在使用时暂停执行,使用另一个线程运行任务,然后在线程完成时返回该行 因此,在您的情况下,如果您希望主例程暂停,直到“诗意”例程完成,您需要使用wait关键字,如下所示:
void async Main()
{
await Poetry();
while (true)
{
Console.WriteLine("Outside, within Main.");
Thread.Sleep(200);
}
}
您还需要更改诗歌的定义,以允许使用wait关键字:
async Task Poetry()
因为这个问题真的让我很感兴趣,所以我继续写了一个你可以编译的示例程序。只需创建一个新的控制台应用程序并将此示例粘贴到中。您可以看到使用“等待”与不使用“等待”的结果
class Program
{
static void Main(string[] args)
{
RunMain();
// pause long enough for all async routines to complete (10 minutes)
System.Threading.Thread.Sleep(10 * 60 * 1000);
}
private static async void RunMain()
{
// with await this will pause for poetry
await Poetry();
// without await this just runs
// Poetry();
for (int main = 0; main < 25; main++)
{
System.Threading.Thread.Sleep(10);
Console.WriteLine("MAIN [" + main + "]");
}
}
private static async Task Poetry()
{
await Task.Delay(10);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("IN THE POETRY ROUTINE [" + i + "]");
System.Threading.Thread.Sleep(10);
}
}
}
类程序
{
静态void Main(字符串[]参数)
{
RunMain();
//暂停足够长的时间以完成所有异步例程(10分钟)
系统线程线程睡眠(10*60*1000);
}
私有静态异步void RunMain()
{
//随着等待,这将暂停诗歌
等待诗歌();
//没有等待,这只是运行
//诗歌();
用于(int main=0;main<25;main++)
{
系统线程线程睡眠(10);
Console.WriteLine(“MAIN[“+MAIN+”]);
}
}
私有静态异步任务()
{
等待任务。延迟(10);
对于(int i=0;i<10;i++)
{
Console.WriteLine(“在诗歌例程[“+i+”]);
系统线程线程睡眠(10);
}
}
}
快乐测试!哦,你仍然可以阅读。阅读,我相信它会变得不那么令人困惑。你所看到的行为完全有道理Task.Delay
在内部使用Win32内核计时器API(即,CreateTimerQueueTimer
)。计时器回调是在池线程上调用的,与Main
线程不同。这就是等待之后诗歌
的其余部分继续执行的地方。这就是默认任务调度器在启动wait
的原始线程上没有同步上下文的情况下的工作方式
由于您不执行等待诗歌()
任务(除非返回任务而不是void
,否则您不能执行该任务),因此它的for
循环继续与并行执行,而Main
中的循环。为什么,更重要的是,您希望它如何“强制”回到主线程上?必须有一些明确的同步点来实现这一点,线程不能简单地在<代码>的中间中断,而<代码>循环> < /p>
在UI应用程序中,核心消息循环可以用作此类同步点。例如,对于WinForms应用程序,WindowsFormsSynchronizationContext
将实现这一点。如果在主UI线程上调用了await Task.Delay()
,则await
之后的代码将在应用程序运行的消息循环的某个未来迭代中在主UI线程上异步继续
因此,如果它是一个UI线程,那么在调用Poetry()
之后的循环中,Poetry
的其余部分将不会与并行执行。相反,它将在控制流返回到消息循环时执行。或者,您可以显式地使用Application.DoEvents()
泵送消息,以便继续,尽管我不建议这样做
另一方面,不要使用async void
,而是使用async Task
。我有一个。本质上,wait
捕获当前的SynchronizationContext
,除非它是null
,在这种情况下,它捕获当前的TaskScheduler
。然后使用该“上下文”来安排该方法的其余部分
由于您正在执行控制台应用程序,因此不存在SynchronizationContext
,并且会捕获默认的TaskScheduler
,以执行async
方法的其余部分。该上下文将async
方法排队到线程池。不可能返回控制台应用程序的主线程,除非您实际为其提供了一个主循环,该主循环带有排队到该主循环的SynchronizationContext
(或TaskScheduler
)。我想在这里回答我自己的问题
你们中的一些人给了我很好的答案,这些答案都很有帮助