如何等待异步方法完成?

Posted

技术标签:

【中文标题】如何等待异步方法完成?【英文标题】:How to wait for async method to complete? 【发布时间】:2013-02-15 11:40:44 【问题描述】:

我正在编写一个将数据传输到 USB HID 类设备的 WinForms 应用程序。我的应用程序使用了出色的通用 HID 库 v6.0,它可以在 here 找到。简而言之,当我需要向设备写入数据时,会调用以下代码:

private async void RequestToSendOutputReport(List<byte[]> byteArrays)

    foreach (byte[] b in byteArrays)
    
        while (condition)
        
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        

        // read some data from device; we need to wait for this to return
        RequestToGetInputReport();
    

当我的代码退出 while 循环时,我需要从设备中读取一些数据。但是,设备无法立即响应,因此我需要等待此呼叫返回,然后才能继续。由于它目前存在,RequestToGetInputReport() 声明如下:

private async void RequestToGetInputReport()

    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();

对于它的价值,GetInputReportViaInterruptTransfer() 的声明如下所示:

internal async Task<int> GetInputReportViaInterruptTransfer()

不幸的是,我对 .NET 4.5 中新的 async/await 技术的工作原理不是很熟悉。我之前读过一些关于 await 关键字的文章,这给我的印象是在 RequestToGetInputReport() 中调用 GetInputReportViaInterruptTransfer() 会等待(也许确实如此?),但它看起来不像对 RequestToGetInputReport() 的调用本身正在等待,因为我似乎几乎立即重新进入 while 循环?

谁能澄清我看到的行为?

【问题讨论】:

【参考方案1】:

关于asyncawait,最重要的一点是await 不会等待相关调用完成。 await 所做的是立即同步地返回操作结果如果操作已经完成,或者如果尚未完成,则安排继续执行 async 的其余部分方法,然后将控制权返回给调用者。当异步操作完成后,将执行预定的完成。

问题标题中特定问题的答案是通过调用适当的Wait 方法来阻止async 方法的返回值(其类型应为TaskTask&lt;T&gt;):

public static async Task<Foo> GetFooAsync()

    // Start asynchronous operation(s) and return associated task.
    ...


public static Foo CallGetFooAsyncAndWaitOnResult()

    var task = GetFooAsync();
    task.Wait(); // Blocks current thread until GetFooAsync task completes
                 // For pedagogical use only: in general, don't do this!
    var result = task.Result;
    return result;

在这段代码 sn-p 中,CallGetFooAsyncAndWaitOnResult 是异步方法 GetFooAsync同步 包装器。但是,这种模式在很大程度上要避免,因为它会在异步操作期间阻塞整个线程池线程。这是对 API 所公开的各种异步机制的低效使用,而 API 则努力提供这些机制。

"await" doesn't wait for the completion of call 的答案对这些关键字有几个更详细的解释。

同时,@Stephen Cleary 关于async void 的指导成立。其他很好的解释可以在http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/和https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html找到

【讨论】:

我发现将await 视为“异步等待”是很有用的——也就是说,它阻塞了 方法(如果有必要的话)而不是线程。因此,谈论RequestToSendOutputReport“等待”RequestToGetInputReport 是有意义的,即使它不是阻塞等待。 @Richard Cook - 非常感谢您的额外解释! 这应该是公认的答案,因为它更清楚地回答了实际问题(即如何在异步方法上进行线程阻塞)。 最好的解决方案是等待异步直到任务完成是 var result = Task.Run(async() => return await yourMethod(); ).Result; @DavidKlempfner: WaitResultawait 被发明之前已经属于 Task 类型。在await 到来之前,Task 是任务并行库的一部分,主要用于并行编程。【参考方案2】:

避免async void。让您的方法返回 Task 而不是 void。然后你可以await他们。

像这样:

private async Task RequestToSendOutputReport(List<byte[]> byteArrays)

    foreach (byte[] b in byteArrays)
    
        while (condition)
        
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        

        // read some data from device; we need to wait for this to return
        await RequestToGetInputReport();
    


private async Task RequestToGetInputReport()

    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();

【讨论】:

非常好,谢谢。我在一个类似的问题上摸不着头脑,不同的是,正如你所说,将 void 更改为 Task 这是一件小事,但要遵循约定,两种方法都应该在它们的名称中添加 Async,例如RequestToGetInputReportAsync() 如果调用者是Main函数呢? @symbiont:然后使用GetAwaiter().GetResult() @AhmedSalah Task 代表方法的执行——所以return 值放在Task.Result 上,异常放在Task.Exception 上。使用void,编译器无处放置异常,因此它们只是在线程池线程上重新引发。【参考方案3】:

等待 AsynMethod 完成任务的最佳解决方案是

var result = Task.Run(async() => await yourAsyncMethod()).Result;

【讨论】:

或者这个用于你的异步“void”:Task.Run(async () => await yourAsyncMethod(); ).Wait(); 这比 yourAsyncMethod().Result 有什么好处? 仅仅访问 .Result 属性实际上并没有等到任务完成执行。事实上,我相信如果在任务完成之前调用它会引发异常。我认为将其包装在 Task.Run() 调用中的优势在于,正如 Richard Cook 在下面提到的那样,“await”实际上并不等待任务完成,而是使用 .Wait() 调用会阻塞整个线程池.这允许您(同步)在单独的线程上运行异步方法。有点混乱,但确实如此。 不错的结果,正是我需要的 快速提醒 ECMA7 功能如 assync() 或 await 在 ECMA7 之前的环境中不起作用。【参考方案4】:

只需让 Wait() 等待任务完成

GetInputReportViaInterruptTransfer().Wait();

【讨论】:

这会阻塞当前线程。所以这通常是一件坏事。 有时这正是您所需要的 有时不仅会阻塞,还会死锁您的应用程序(尤其是在 WPF 中使用调度程序/主线程时)【参考方案5】:

以上所有答案都是正确的,您永远不应该同步等待任务......除非您必须这样做!有时您想在您无法控制的接口实现中调用异步方法,并且无法执行“一直向下同步”。

这是一个小类,至少可以包含损坏:

public class RunSynchronous

    public static void Do(Func<Task> func) => Task.Run(func).GetAwaiter().GetResult();
    public static T Do<T>(Func<Task<T>> func) => Task.Run(func).GetAwaiter().GetResult();
    public static void Do(Func<ValueTask> func) => Do(() => func().AsTask());
    public static T Do<T>(Func<ValueTask<T>> func) => Do(() => func().AsTask());

此类使用 .GetAwaiter.GetResult 模式将异步实际转换为同步。但是,如果您在 WPF 或绑定到特定线程的另一个 SynchronizationContext 中自行执行此操作,则会出现死锁。此代码通过将异步操作转移到与特定线程不同步的线程池来避免死锁。只要你不发疯并阻塞所有线程池线程,你应该没问题。

用法是这样的

    return RunSynchronous.Do(()=>AsyncOperation(a,b,c));

将在线程池上启动 AsynchronousOperation(a,b,c) 并​​等待它返回。只要你没有明确地同步回原始线程,你应该没问题。

【讨论】:

【参考方案6】:

下面的 sn-p 显示了一种确保等待的方法在返回给调用者之前完成的方法。但是,我不会说这是一个好习惯。如果您不这么认为,请编辑我的答案并附上解释。

public async Task AnAsyncMethodThatCompletes()

    await SomeAsyncMethod();
    DoSomeMoreStuff();
    await Task.Factory.StartNew(() =>  ); // <-- This line here, at the end


await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")

【讨论】:

反对者,请解释一下,就像我在答案中问的那样?因为据我所知,这很有效...... 问题是你可以做到await + Console.WriteLine 的唯一方法是变成Task,这放弃了两者之间的控制。所以你的“解决方案”最终会产生一个Task&lt;T&gt;,它不能解决问题。执行Task.Wait will 实际上会停止处理(可能会出现死锁等)。换句话说,await 实际上并没有等待,它只是将两个异步可执行部分组合成一个 Task(有人可以观看或等待)【参考方案7】:

实际上我发现这对返回 IAsyncAction 的函数更有帮助。

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;

【讨论】:

以上是关于如何等待异步方法完成?的主要内容,如果未能解决你的问题,请参考以下文章

如何让一个方法在继续执行之前等待另一个(异步)方法完成?

C#如何在不等待完成的情况下启动异步方法?

等待 dispatch_semaphore 以等待许多异步任务完成的正确方法

如何等待所有异步的初始化条件全部加载完成后,再执行方法

Android(Kotlin) - 如何等待异步任务完成?

如何在单元测试之前等待组件的挂载回调中的异步调用完成?