166

我正在编写一个将数据传输到 USB HID 类设备的 WinForms 应用程序。我的应用程序使用优秀的 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 中新的 async/await 技术的工作原理不是很熟悉。我之前读过一些关于 await 关键字的文章,这给我的印象是在 RequestToGetInputReport() 中调用 GetInputReportViaInterruptTransfer() 会等待(也许确实如此?),但它看起来不像对 RequestToGetInputReport() 的调用本身正在等待,因为我似乎几乎立即重新进入 while 循环?

谁能澄清我看到的行为?

4

7 回答 7

267

最重要的是要了解async并且await不等待相关调用完成。如果操作已经完成,则立即同步地返回操作结果,或者如果尚未完成,则安排继续执行该方法的其余部分,然后将控制权返回给调用者。异步操作完成后,将执行计划的完成。await awaitasync

问题标题中特定问题的答案是通过调用适当的方法来阻止async方法的返回值(应该是Taskor类型) :Task<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 所公开的各种异步机制的低效使用,这些机制需要付出很大的努力来提供它们。

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

同时,@Stephen Cleary 对async void持有的指导。其他很好的解释为什么可以在http://www.toncodes.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

于 2013-03-01T05:53:52.967 回答
150

避免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();
}
于 2013-03-01T03:10:27.980 回答
104

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

var result = Task.Run(async() => await yourAsyncMethod()).Result;
于 2017-06-16T00:14:29.770 回答
3

只需放置 Wait() 等待任务完成

GetInputReportViaInterruptTransfer().Wait();

于 2019-12-14T08:07:31.660 回答
-1

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

这是一个至少包含损坏的小类:

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) 并​​等待它返回。只要您没有明确地同步回原始线程,您就应该没问题。

于 2022-02-01T00:51:44.120 回答
-5

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

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

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
于 2016-03-08T21:45:50.607 回答
-5

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

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
于 2019-02-10T21:14:15.100 回答