C# 等待归来后的意外行为

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

我知道关于async/Wait有很多问题,但我找不到任何答案

我遇到了一些我不理解的事情,考虑下面的代码:

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
)。

我想在这里回答我自己的问题

你们中的一些人给了我很好的答案,这些答案都很有帮助