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

149

Я пишу приложение WinForms, которое передает данные на устройство класса USB HID. В моем приложении используется отличная Generic HID library 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. Ранее я немного читал о ключевом слове await, и у меня сложилось впечатление, что вызов GetInputReportViaInterruptTransfer () внутри RequestToGetInputReport () будет ждать (и, может быть, это так?), Но это не похоже на вызов RequestToGetInputReport () сам ждет, потому что мне кажется, что я почти сразу повторно вхожу в цикл while?

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

bmt22033
источник

Ответы:

138

Избегайте 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
Очень хорошо, спасибо. Я ломал голову над подобной проблемой, и разница заключалась в том, чтобы изменить ее voidна то, Taskчто вы сказали.
Джереми
8
Это мелочь, но, чтобы следовать соглашению, оба метода должны иметь Async, добавленный к их именам, например RequestToGetInputReportAsync ()
tymtam
7
а что, если вызывающим является функция Main?
симбионт
14
@symbiont: Тогда используйтеGetAwaiter().GetResult()
Стивен Клири
4
@AhmedSalah Taskпредставляет выполнение метода, поэтому returnзначения размещаются Task.Result, а исключения размещаются Task.Exception. С void, компилятор не имеет нигде места исключения, поэтому они просто ререйз на поток пула потоков.
Стивен Клири
238

Самое главное , что нужно знать о 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

Ричард Кук
источник
18
Я считаю полезным думать (и говорить) об await«асинхронном ожидании» - то есть, оно блокирует метод (если необходимо), но не поток . Поэтому имеет смысл говорить об RequestToSendOutputReport«ожидании», RequestToGetInputReportдаже если это не ожидание с блокировкой .
Стивен Клири
@Richard Cook - большое спасибо за дополнительные объяснения!
bmt22033 01
10
Это должен быть принятый ответ, поскольку он более четко отвечает на фактический вопрос (например, как поточно блокировать асинхронный метод).
csvan
Лучшее решение - подождать асинхронно, пока задача не будет завершена - var result = Task.Run (async () => {return await yourMethod ();}). Result;
Ram chittala
78

Лучшее решение для ожидания AsynMethod до завершения задачи -

var result = Task.Run(async() => await yourAsyncMethod()).Result;
Рам читтала
источник
16
Или это для вашего асинхронного "void": Task.Run (async () => {await yourAsyncMethod ();}). Wait ();
Jiří Herník
1
В чем преимущество этого метода по сравнению с yourAsyncMethod (). Result?
Джастин Дж. Старк
1
Простой доступ к свойству .Result фактически не означает ожидания завершения выполнения задачи. Фактически, я считаю, что он выдает исключение, если он вызывается до завершения задачи. Я думаю, что преимущество обертывания этого в вызове Task.Run () состоит в том, что, как упоминает ниже Ричард Кук, "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();
}
шокированный леми
источник
-1

просто поместите Wait (), чтобы дождаться завершения задачи

GetInputReportViaInterruptTransfer().Wait();

Фирас Низам
источник
Это блокирует текущий поток. Так что обычно это плохой поступок.
Pure.Krome
-5

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

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

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
Джертер
источник
Сторонники отрицательного мнения, не могли бы вы объяснить, как я спросил в ответ? Потому что, насколько я знаю, это хорошо работает ...
Джертер
3
Проблема в том, что единственный способ сделать await+ the Console.WriteLine- превратить его в a Task, что теряет контроль между ними. так что ваше «решение» в конечном итоге даст a Task<T>, который не решает проблему. Двигаешься Task.Waitбудет фактически прекратить обработку (с тупиковыми возможностями и т.д.). Другими словами, на awaitсамом деле не ждёт, а просто объединяет две асинхронно исполняемые части в одну Task(которую кто-то может посмотреть или подождать)
Рубен Бартелинк,
-5

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

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
Бариш Таньери
источник