如何等待异步方法完成?


138

我正在编写一个WinForms应用程序,该应用程序将数据传输到USB HID类设备。我的应用程序使用了出色的Generic HID库v6.0,可以在这里找到。简而言之,当我需要将数据写入设备时,这就是被调用的代码:

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中新的异步/等待技术的工作不是很熟悉。之前我对await关键字做了一些阅读,给我的印象是对RequestToGetInputReport()内的GetInputReportViaInterruptTransfer()的调用将等待(也许会吗?),但似乎不像对RequestToGetInputReport()的调用本身正在等待,因为我似乎几乎立即重新进入了while循环?

谁能澄清我所看到的行为?

Answers:


131

避免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();
}

1
很好谢谢。我被刮伤上类似的问题我的头和差异改变voidTask如你说的而已。
杰里米

8
这是一件小事,但是要遵循惯例,两个方法都应在其名称上添加Async,例如RequestToGetInputReportReportAsync()
tymtam

6
如果调用方是Main函数,该怎么办?
symbiont

14
@symbiont:然后使用GetAwaiter().GetResult()
Stephen Cleary,

4
@AhmedSalah Task表示方法的执行,因此将return值放在上Task.Result,将异常放在上Task.Exception。使用void,编译器无处放置异常,因此它们仅在线程池线程上重新引发。
斯蒂芬·克莱里

229

最重要的是要了解asyncawaitawait 等待完成相关的呼叫。什么await做的是立即地,同步返回操作的结果如果操作已经完成,或者,如果没有,安排的延续,执行的剩余async方法,然后将控制返回给调用者。异步操作完成后,将执行计划的完成。

问题标题中特定问题的答案是通过调用适当的方法来阻止async方法的返回值(应为type TaskTask<T>Wait

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;
}

在此代码段中,CallGetFooAsyncAndWaitOnResult是围绕异步方法的同步包装器GetFooAsync。但是,由于在异步操作期间它将阻塞整个线程池线程,因此在很大程度上应避免使用这种模式。对API公开的各种异步机制的使用效率低下,而API会竭尽全力提供这些异步机制。

“ await”处的答案不等待呼叫完成,对这些关键字有一些更详细的说明。

同时,@ 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


18
我发现await将“异步等待”作为思考(讨论)很有用-也就是说,它阻塞了方法(如果需要),而不阻塞了线程。因此,即使不是阻塞性等待,谈论RequestToSendOutputReport“等待” RequestToGetInputReport也是有意义的。
Stephen Cleary

@Richard Cook-非常感谢您的其他解释!
bmt22033

10
这应该是公认的答案,因为它可以更清楚地回答实际问题(即,如何在异步方法上进行线程式阻止)。
csvan

最好的解决方案是等待异步,直到任务完成为止。var result = Task.Run(async()=> {return await yourMethod();})。Result;
Ram chittala

70

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

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

15
或针对您的异步“ void”:Task.Run(async()=> {await yourAsyncMethod();})。Wait();
吉日Herník

1
与yourAsyncMethod()。Result相比,这样做有什么好处?
贾斯汀·斯塔克

1
简单地访问.Result属性实际上并不会等到任务完成执行。实际上,我相信如果在任务完成之前调用它会引发异常。我认为将其包装在Task.Run()调用中的好处是,正如下面的Richard Cook所述,“ await”实际上并不等待任务完成,而是使用.Wait()调用会阻塞整个线程池。这允许您(同步)在单独的线程上运行异步方法。有点混乱,但是确实存在。
卢卡斯·勒布朗

很好地把结果扔在那里,正是我所需要的
格里

快速提醒ECMA7功能(如assync()或await)在ECMA7之前的环境中无法使用。
Mbotet

0

这是使用标志的解决方法:

//outside your event or method, but inside your class
private bool IsExecuted = false;

private async Task MethodA()
{

//Do Stuff Here

IsExecuted = true;
}

.
.
.

//Inside your event or method

{
await MethodA();

while (!isExecuted) Thread.Sleep(200); // <-------

await MethodB();
}



-5

下面的代码片段显示了一种确保等待的方法在返回调用方之前完成的方法。但是,我不会说这是一个好习惯。否则,请编辑我的回答并附上说明。

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

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

下注者,关心地解释,就像我在回答中问的那样?据我所知,这是行之有效的……
Jerther

3
问题在于,唯一的方法是将await+ Console.WriteLine变成+ Task,从而放弃两者之间的控制。因此您的“解决方案”最终会产生一个Task<T>,不能解决问题。做一个Task.Wait实际上将停止处理(与僵局的可能性等)。换句话说,await实际上并没有等待,它只是将两个异步可执行的部分合并为一个Task(有人可以观看或等待)
Ruben Bartelink
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.