【问题标题】:Trouble understanding async and await无法理解异步和等待
【发布时间】:2017-11-04 04:01:54
【问题描述】:

我一直在尝试理解 C# 中的 async/await 和 Task,但尽管观看了 youtube 视频、阅读文档和学习了复数课程,但我还是失败了。

我希望有人能够帮助回答这些稍微抽象的问题,以帮助我解决问题。

1. 为什么当 async 关键字本身什么都不做而 await 关键字添加了一个暂停点时,他们说 async/await 启用了“asynchonrous”方法?不会添加暂停点来强制方法同步执行,即在继续之前完成等待标记的任务。

2. 显然你不应该使用 async void 除了事件处理程序,那么你如何正常调用 async 方法呢?似乎为了使用 await 关键字调用异步方法,调用它的方法/类本身需要标记为异步。我见过的所有示例都使用事件处理程序“启动”了一个异步 void 方法。您将如何“逃避”这种 async/await 包装来运行该方法?

3.

public async Task SaveScreenshot(string filename, IWebDriver driver)
{
    var screenshot = driver.TakeScreenshot();
    await Task.Run(() =>
    {
        Thread.Sleep(2000);
        screenshot.SaveAsFile(filename, ScreenshotImageFormat.Bmp);
        Console.WriteLine("Screenshot saved");
    });
    Console.WriteLine("End of method");
}

回到 1. 这看起来像一个同步方法。当它到达Task.Run 时执行暂停,因此Console.WriteLine("End of method"); 在任务完成之前不会被执行。也许整个方法本身将在代码中触发时异步执行?但回到 2,您需要使用 await 调用它,否则您会收到消息“因为未等待此调用..”因此添加 await 将导致该执行点同步等等。

任何理解这一点的帮助将不胜感激。

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    没有添加暂停点来强制方法同步执行,即在继续之前完成等待标记的任务。

    不,您想到的词是“顺序”,而不是“同步”。 await 导致异步顺序代码。 “顺序”的意思是“一次一个”; “同步”的意思是“阻塞直到完成”。

    你通常如何调用异步方法?

    使用await

    您将如何“逃避”这种 async/await 包装来运行该方法?

    理想情况下,您不要。你去async all the way。现代框架(包括 ASP.NET MVC、Azure Functions / WebJobs、NUnit / xUnit / MSTest 等)都允许您拥有返回 Task 的入口点。不太现代的框架(包括 WinForms、WPF、Xamarin Forms、ASP.NET WebForms 等)都允许 async void 入口点。

    因此,理想情况下,您从同步代码中调用异步代码。如果您考虑一下什么是异步代码,这是有道理的:它的全部意义在于阻塞调用线程,所以如果您在异步代码上阻塞调用线程,那么你首先失去了异步代码的所有好处。

    也就是说,在极少数情况下确实需要同步处理代码。例如,如果您正在过渡到异步,或者如果您受到库/框架的限制,该库/框架强制您的代码是同步的并且不能与async void 一起使用。在这种情况下,您可以使用hacks in my article on brownfield async 之一。

    【讨论】:

    • 谢谢斯蒂芬。你是对的,我将顺序与异步混淆了。这种解释是否有效 - 假设我有一个包含 Task.Run 的普通方法,其中包含一些代码,之前和之后。如果我触发该方法,当它到达 Task.Run 时,它将异步执行(?),但没有顺序控制,因此(在方法中)之后的代码将继续执行。这个方法本身会阻塞调用线程吗?与 async await 相比,如果在方法中的 await 之前执行将等待任务运行,但不会阻塞调用线程。
    • Task.Run 只是将一些工作扔到线程池中。所以我认为Task.Run 中的代码完全独立于调用代码。你可以考虑它是异步运行的使用术语“并行”来描述Task.Run 而不是“异步”。您可能会发现我的async intro 很有帮助。
    【解决方案2】:

    你的理解很好:)。您似乎缺少的要点是 .NET 中的“异步”方法意味着可以在不阻塞调用线程的情况下停止执行的方法。

    正如您在 (1) 中指出的,async 关键字基本上可以使用await,并且要求返回类型为voidTask/Task<T>await 只是指示当前方法暂停执行,直到任务完成。

    您在这里缺少的是它暂停仅暂停当前方法。它确实阻塞了方法正在执行的线程。这在诸如 WPF 应用程序的 UI 线程之类的情况下很重要。暂停方法执行,一切继续运行,阻塞线程,应用程序停止响应。

    您通常希望您的async 调用一直到顶部(如事件处理程序),这样可以提供最大的灵活性并防止出现死锁情况。然而;您可以等待Task 返回方法完成Wait

    someAsyncMethod.Wait()
    

    或者获取返回值:

     var result = someAsyncMethod.Result;
    

    请注意,这两个 都是同步的,并且会阻塞调用线程。如果异步任务正在等待调用线程上的其他工作完成,这样做可能会导致死锁。

    以上内容应回答您在 (3) 中的问题;方法本身似乎是同步执行的(这是await/async 的魔力),但任务不会阻塞调用线程。

    【讨论】:

    • 非常感谢布拉德利。因此,例如,如果您调用异步方法并且它包含一个运行 5 分钟的等待任务,那么当它到达等待时,“焦点”是否会返回到调用线程?有点像'好吧,你可以在我执行这个任务时控制自己做你自己的事情,之后我还会执行任何剩余的代码'类型的方式?
    • @Konzy262 是的;确切的工作方式取决于斯蒂芬的回答中所解释的上下文同步
    【解决方案3】:

    它是异步的,因为您不必等待方法返回。在您的代码中,您可以调用异步方法并将任务保存在变量中。继续做其他事情。稍后,当需要方法结果时,您等待响应(任务)。

    // Synchronous method.
    static void Main(string[] args)
    {
        // Call async methods, but don't await them until needed.
        Task<string> task1 = DoAsync();
        Task<string> task2 = DoAsync();
        Task<string> task3 = DoAsync();
    
        // Do other stuff.
    
        // Now, it is time to await the async methods to finish.
        Task.WaitAll(task1, task2, task3);
    
        // Do something with the results.
        Console.WriteLine(task1.Result);
        Console.ReadKey();
    }
    
    private static async Task<string> DoAsync()
    {
        Console.WriteLine("Started");
        await Task.Delay(3000);
        Console.WriteLine("Finished");
    
        return "Success";
    }
    
    // Output:
    // Started
    // Started
    // Started
    // Finished
    // Finished
    // Finished
    // Success
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-15
      • 2019-09-16
      • 1970-01-01
      • 2018-09-26
      • 2022-10-08
      • 1970-01-01
      • 1970-01-01
      • 2014-01-09
      相关资源
      最近更新 更多