如何等待异步方法完成?

我正在编写一个 WinForms 应用程序,传输数据到一个 USB HID 类设备。我的应用程序使用优秀的通用 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();
}

值得一提的是,GetInputReportViaInterrupTransfer ()的声明如下所示:

internal async Task<int> GetInputReportViaInterruptTransfer()

不幸的是,我不太熟悉中的新的异步/等待技术的工作原理。NET 4.5.我之前阅读了一些关于 wait 关键字的内容,它给我的印象是,在 RequestToGetInputReport ()内部对 GetInputReportViaInterrupTransfer ()的调用会等待(也许真的会等待?)但是对 RequestToGetInputReport ()本身的调用似乎并没有等待,因为我似乎马上就要重新进入 while 循环了?

有人能解释一下我看到的行为吗?

433196 次浏览

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

关于 asyncawait最重要的事情是,await 没有等待相关调用完成。await所做的是立即同步返回操作的结果 如果操作已经完成,或者,如果没有,安排一个延续来执行 async方法的其余部分,然后将控制返回给调用者。当异步操作完成时,计划的完成将随后执行。

问题标题中特定问题的答案是,通过调用适当的 Wait方法来阻塞 async方法的返回值(返回值应该是 TaskTask<T>类型) :

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 花费了大量精力来提供这些异步机制。

“等待”不等待呼叫的完成的答案对这些关键字有几个更详细的解释。

与此同时,@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找到

下面的代码片段显示了在返回给调用方之前确保等待方法完成的方法。然而,我不认为这是好的练习。如果你不这么认为,请修改我的答案并加以解释。

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


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

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

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

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

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

只需将 Wait ()设置为等待任务完成

GetInputReportViaInterruptTransfer().Wait();

以上所有的答案都是正确的,你永远不应该同步地等待一个任务... ... 除非你必须这样做!有时候,您想要在您不能控制的接口实现内部调用一个异步方法,而且没有办法“一直执行异步”

下面是一个小类,至少可以控制损害:

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

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

用法是这样的

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

将在线程池上启动 AsynousOperation (a,b,c)并等待它返回。只要不显式地与原始线程同步,就应该没问题。