Bagaimana cara menunggu metode async selesai?

138

Saya sedang menulis aplikasi WinForms yang mentransfer data ke perangkat kelas HID USB. Aplikasi saya menggunakan pustaka HID Generic v6.0 yang luar biasa yang dapat ditemukan di sini . Singkatnya, ketika saya perlu menulis data ke perangkat, ini adalah kode yang dipanggil:

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

Ketika kode saya keluar dari loop sementara, saya perlu membaca beberapa data dari perangkat. Namun, perangkat tidak dapat segera merespons sehingga saya harus menunggu panggilan ini kembali sebelum saya melanjutkan. Karena saat ini ada, RequestToGetInputReport () dinyatakan seperti ini:

private async void RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

Untuk apa nilainya, deklarasi untuk GetInputReportViaInterruptTransfer () terlihat seperti ini:

internal async Task<int> GetInputReportViaInterruptTransfer()

Sayangnya, saya tidak terlalu terbiasa dengan cara kerja teknologi async / menunggu baru di .NET 4.5. Saya melakukan sedikit bacaan sebelumnya tentang kata kunci yang menunggu dan yang memberi saya kesan bahwa panggilan untuk GetInputReportViaInterruptTransfer () di dalam RequestToGetInputReport () akan menunggu (dan mungkin ya?) Tetapi sepertinya panggilan ke RequestToGetInputReport () itu sendiri sedang menunggu karena saya tampaknya akan segera memasuki loop sementara?

Adakah yang bisa mengklarifikasi perilaku yang saya lihat?

bmt22033
sumber

Jawaban:

131

Hindari async void. Memiliki metode Anda kembali Taskbukan void. Maka kamu bisa awaitmereka.

Seperti ini:

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();
}
Stephen Cleary
sumber
1
Sangat baik terima kasih. Saya menggaruk-garuk kepala saya pada masalah yang sama dan perbedaannya berubah voidmenjadi Taskseperti yang Anda katakan.
Jeremy
8
Ini hal kecil, tetapi untuk mengikuti konvensi kedua metode harus memiliki Async ditambahkan ke nama mereka, misalnya RequestToGetInputReportAsync ()
tymtam
6
dan bagaimana jika penelepon adalah fungsi Utama?
symbiont
14
@ symbiont: Kemudian gunakanGetAwaiter().GetResult()
Stephen Cleary
4
@AhmedSalah Merupakan Taskeksekusi dari metode - sehingga returnnilai ditempatkan Task.Result, dan pengecualian ditempatkan pada Task.Exception. Dengan void, kompiler tidak memiliki tempat untuk menempatkan pengecualian, jadi mereka hanya diangkat kembali pada utas thread pool.
Stephen Cleary
229

Hal yang paling penting untuk mengetahui tentang asyncdan awaitadalah bahwa await tidak menunggu panggilan terkait untuk lengkap. Apa yang awaitdilakukan adalah mengembalikan hasil operasi segera dan serempak jika operasi telah selesai atau, jika belum, untuk menjadwalkan kelanjutan untuk menjalankan sisa asyncmetode dan kemudian mengembalikan kontrol ke penelepon. Ketika operasi asinkron selesai, penyelesaian yang dijadwalkan kemudian akan dijalankan.

Jawaban untuk pertanyaan spesifik dalam judul pertanyaan Anda adalah untuk memblokir asyncnilai pengembalian metode (yang harus bertipe Taskatau Task<T>) dengan memanggil Waitmetode yang sesuai :

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

Dalam cuplikan kode ini, CallGetFooAsyncAndWaitOnResultadalah pembungkus sinkron di sekitar metode asinkron GetFooAsync. Namun, pola ini harus dihindari untuk sebagian besar karena akan memblokir seluruh thread pool selama operasi asinkron. Ini merupakan penggunaan yang tidak efisien dari berbagai mekanisme asinkron yang diekspos oleh API yang berupaya keras untuk menyediakannya.

Jawaban di "menunggu" tidak menunggu penyelesaian panggilan memiliki beberapa, lebih rinci, penjelasan kata kunci ini.

Sementara itu, panduan @Stephen Cleary tentang penangguhan async void. Penjelasan bagus lainnya untuk alasannya dapat ditemukan di http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ dan https://jaylee.org/archive/ 2012/07/08 / c-tajam-async-tips-dan-trik-bagian-2-async-void.html

Richard Cook
sumber
18
Saya merasa berguna untuk berpikir (dan berbicara) tentang awaitsebagai "menunggu asinkron" - yaitu, ia memblokir metode (jika perlu) tetapi bukan utasnya . Jadi masuk akal untuk berbicara tentang RequestToSendOutputReport"menunggu" RequestToGetInputReportmeskipun itu bukan menunggu pemblokiran .
Stephen Cleary
@ Richard Cook - terima kasih banyak untuk penjelasan tambahannya!
bmt22033
10
Ini seharusnya menjadi jawaban yang diterima, karena lebih jelas menjawab pertanyaan yang sebenarnya (yaitu bagaimana cara memblokir secara bijak pada metode async).
csvan
solusi terbaik adalah menunggu async sampai tugas selesai adalah var result = Task.Run (async () => {return tunggu yourMethod ();}). Hasil;
Ram chittala
70

Solusi terbaik untuk menunggu AsynMethod hingga menyelesaikan tugasnya

var result = Task.Run(async() => await yourAsyncMethod()).Result;
Ram chittala
sumber
15
Atau ini untuk "void" async Anda: Task.Run (async () => {tunggu yourAsyncMethod ();}). Tunggu ();
Jiří Herník
1
Apa manfaatnya dari hasil asyncMethod () Anda?
Justin J Stark
1
Cukup mengakses properti .Result tidak benar-benar menunggu sampai tugas selesai dijalankan. Bahkan, saya percaya itu melempar pengecualian jika dipanggil sebelum tugas selesai. Saya pikir keuntungan dari membungkus ini dalam panggilan Task.Run () adalah, seperti yang dikatakan Richard Cook di bawah ini, "menunggu" sebenarnya tidak menunggu tugas selesai, tetapi menggunakan panggilan .Wait () memblokir seluruh kumpulan thread Anda . Ini memungkinkan Anda untuk (secara sinkron) menjalankan metode async pada utas terpisah. Agak membingungkan, tapi itu dia.
Lucas Leblanc
Senang melempar hasil di sana, hanya apa yang saya butuhkan
Gerry
Pengingat cepat ECMA7 seperti fitur assync () atau menunggu pekerjaan di lingkungan pra-ECMA7.
Mbotet
0

Berikut ini solusinya menggunakan bendera:

//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();
}
lemi shocky
sumber
-1

cukup tunggu () untuk menunggu sampai tugas selesai

GetInputReportViaInterruptTransfer().Wait();

Firas Nizam
sumber
Ini memblokir utas saat ini. Jadi ini biasanya hal yang buruk untuk dilakukan.
Pure.Krome
-4

Sebenarnya saya menemukan ini lebih bermanfaat untuk fungsi yang mengembalikan IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
Barış Tanyeri
sumber
-5

Cuplikan berikut menunjukkan cara untuk memastikan metode yang ditunggu selesai sebelum kembali ke pemanggil. NAMUN, saya tidak akan mengatakan itu latihan yang baik. Harap edit jawaban saya dengan penjelasan jika Anda berpikir sebaliknya.

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

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
Jerther
sumber
Para downvoters, mau menjelaskan, seperti yang saya tanyakan dalam jawabannya? Karena ini bekerja dengan baik sejauh yang saya tahu ...
Jerther
3
Masalahnya adalah bahwa satu-satunya cara Anda dapat melakukan await+ Console.WriteLineadalah dengan menjadi Task, yang memberikan kontrol di antara keduanya. jadi 'solusi' Anda pada akhirnya akan menghasilkan Task<T>, yang tidak mengatasi masalah. Melakukan Task.Waitwasiat akan benar-benar berhenti memproses (dengan kemungkinan kebuntuan dll). Dengan kata lain, awaittidak benar-benar menunggu, itu hanya menggabungkan dua bagian yang dapat dieksekusi secara tidak serempak menjadi satu Task(yang dapat ditonton atau ditunggu seseorang)
Ruben Bartelink