Как дождаться завершения асинхронного метода?

Я пишу приложение 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();
}

Для чего это стоит, объявление для GetInputReportViaInterruptTransfer() выглядит так:

internal async Task<int> GetInputReportViaInterruptTransfer()

К сожалению, я не очень хорошо разбираюсь в работе новых технологий async/await в .NET 4.5. Я немного читал ранее о ключевом слове "Ожидание", и это создавало впечатление, что вызов GetInputReportViaInterruptTransfer() внутри RequestToGetInputReport() будет ждать (и, возможно, это так?), Но это не похоже на вызов RequestToGetInputReport() сам ждет, потому что я, кажется, сразу возвращаюсь в цикл while?

Может ли кто-нибудь уточнить поведение, которое я вижу?

Ответ 1

Избегайте 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();
}

Ответ 2

Самое важное, что нужно знать о async и await, это то, что await не ожидает завершения соответствующего вызова. Что делает await, так это немедленно и синхронно возвращает результат операции, если операция уже завершена, или, если это не так, назначает продолжение для выполнения оставшейся части метода async, а затем возвращает управление звонящий. По завершении асинхронной операции будет выполнено запланированное завершение.

Ответ на конкретный вопрос в заголовке вашего вопроса состоит в том, чтобы заблокировать возвращаемое значение метода async (которое должно быть типа Task или 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.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

Ответ 3

Лучшее решение подождать, пока AsynMethod не выполнит задание, -

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

Ответ 4

На самом деле я нашел это более полезным для функций, которые возвращают IAsyncAction.

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

Ответ 5

В следующем фрагменте показан способ обеспечения ожидаемого метода до его возврата к вызывающему. ОДНАКО, я бы не сказал, что это хорошая практика. Пожалуйста, отредактируйте мой ответ с объяснениями, если вы думаете иначе.

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

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