menunggu vs Task.Wait - Deadlock?

194

Saya tidak begitu mengerti perbedaan antara Task.Waitdan await.

Saya memiliki sesuatu yang mirip dengan fungsi-fungsi berikut dalam layanan ASP.NET WebAPI:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}

Dimana Getakan menemui jalan buntu.

Apa yang bisa menyebabkan ini? Mengapa ini tidak menyebabkan masalah ketika saya menggunakan menunggu menunggu alih-alih await Task.Delay?

ronag
sumber
@Ervy: Saya akan kembali dengan repo segera setelah saya punya waktu. Untuk saat ini bekerja dengan Task.Delay(1).Wait()yang cukup baik.
ronag
2
Task.Delay(1).Wait()pada dasarnya sama persis dengan Thread.Sleep(1000). Dalam kode produksi aktual, ini jarang sesuai.
Melayani
@ronag: Anda WaitAllmenyebabkan kebuntuan. Lihat tautan ke blog saya di jawaban saya untuk lebih jelasnya. Anda harus menggunakannya await Task.WhenAllsebagai gantinya.
Stephen Cleary
6
@ronag Karena Anda memiliki ConfigureAwait(false)sebuah tunggal panggilan untuk Baratau Rostidak akan deadlock, tetapi karena Anda memiliki enumerable yang menciptakan lebih dari satu dan kemudian menunggu semua orang, bar pertama akan kebuntuan kedua. Jika Anda await Task.WhenAllalih-alih menunggu semua tugas, sehingga Anda tidak memblokir konteks ASP, Anda akan melihat metode kembali secara normal.
Servy
2
@ronag Pilihan Anda yang lain adalah menambahkan .ConfigureAwait(false) semua jalan di atas pohon sampai Anda memblokir, dengan cara itu tidak ada yang pernah mencoba untuk kembali ke konteks utama; itu akan berhasil. Pilihan lain adalah memutar konteks sinkronisasi dalam. Link . Jika Anda menempatkan Task.WhenAlldalam AsyncPump.Runsecara efektif akan memblokir di seluruh hal tanpa Anda perlu untuk ConfigureAwaitdi mana saja, tapi itu mungkin solusi yang terlalu kompleks.
Servy

Jawaban:

268

Waitdan await- walaupun serupa secara konseptual - sebenarnya sangat berbeda.

Waitakan secara sinkron memblokir sampai tugas selesai. Jadi utas saat ini benar-benar diblokir menunggu tugas selesai. Sebagai aturan umum, Anda harus menggunakan " asyncsepenuhnya"; yaitu, jangan memblokir asynckode. Di blog saya, saya masuk ke detail bagaimana memblokir kode asinkron menyebabkan kebuntuan .

awaitakan asinkron menunggu sampai tugas selesai. Ini berarti metode saat ini "dijeda" (statusnya ditangkap) dan metode mengembalikan tugas yang tidak lengkap ke pemanggilnya. Kemudian, ketika awaitekspresi selesai, sisa metode dijadwalkan sebagai kelanjutan.

Anda juga menyebutkan "blok kooperatif", yang saya anggap maksud Anda tugas yang sedang Anda Waitjalankan dapat dijalankan pada utas menunggu. Ada situasi di mana ini bisa terjadi, tetapi ini adalah optimasi. Ada banyak situasi di mana itu tidak bisa terjadi, seperti jika tugas adalah untuk penjadwal lain, atau jika sudah dimulai atau jika itu adalah tugas non-kode (seperti dalam contoh kode Anda: Waittidak dapat menjalankan Delaytugas inline karena tidak ada kode untuk itu).

Anda mungkin menemukan async/ awaitintro saya bermanfaat.

Stephen Cleary
sumber
4
Saya pikir ada kesalahpahaman, Waitbekerja dengan baik awaitdeadlock.
ronag
5
Tidak, penjadwal tugas tidak akan melakukan itu. Waitmemblokir utas, dan tidak dapat digunakan untuk hal lain.
Stephen Cleary
8
@ronag Dugaan saya adalah Anda baru saja mencampur nama metode dan kebuntuan Anda sebenarnya disebabkan oleh kode pemblokiran dan bekerja dengan awaitkode tersebut. Entah itu, atau kebuntuan tidak terkait dengan salah satu dan Anda salah mendiagnosis masalah.
Servy
3
@hexterminator: Ini sesuai desain - ini berfungsi baik untuk aplikasi UI, tetapi cenderung menghalangi aplikasi ASP.NET. ASP.NET Core telah memperbaiki ini dengan menghapus SynchronizationContext, jadi memblokir dalam permintaan ASP.NET Core tidak ada lagi deadlock.
Stephen Cleary
1
di msdn di sini, ia mengatakan bahwa menunggu berjalan di utas terpisah secara asinkron. Apakah saya melewatkan sesuatu? msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx
batmaci
6

Berdasarkan apa yang saya baca dari berbagai sumber:

Sebuah awaitekspresi tidak memblokir thread di mana ia mengeksekusi. Alih-alih, itu menyebabkan kompiler untuk mendaftar sisa asyncmetode sebagai kelanjutan dari tugas yang ditunggu. Kontrol kemudian kembali ke pemanggil asyncmetode. Ketika tugas selesai, ia memanggil kelanjutannya, dan eksekusi asyncmetode dilanjutkan di tempat yang ditinggalkannya.

Untuk menunggu satu tasksampai selesai, Anda dapat memanggil Task.Waitmetodenya. Panggilan ke Waitmetode memblokir utas panggilan sampai instance kelas tunggal telah menyelesaikan eksekusi. Wait()Metode parameterless digunakan untuk menunggu tanpa syarat sampai tugas selesai. Tugas mensimulasikan pekerjaan dengan memanggil Thread.Sleepmetode untuk tidur selama dua detik.

Artikel ini juga merupakan bacaan yang bagus.

Ayushmati
sumber
3
"Bukankah itu secara teknis salah? Bisakah seseorang tolong klarifikasi?" - bisakah saya mengklarifikasi; apakah Anda menanyakan hal itu sebagai pertanyaan? (Saya hanya ingin menjadi jelas apakah Anda bertanya vs menjawab). Jika Anda bertanya: itu mungkin berfungsi lebih baik sebagai pertanyaan terpisah; tidak mungkin untuk mengumpulkan tanggapan baru di sini sebagai jawaban
Marc Gravell
1
Saya telah menjawab pertanyaan dan mengajukan pertanyaan terpisah untuk keraguan yang saya miliki di sini stackoverflow.com/questions/53654006/... Terima kasih @MarcGravell. Bisakah Anda menghapus suara penghapusan untuk jawabannya sekarang?
Ayushmati
"Bisakah kamu menghapus suara penghapusan untuk jawabannya sekarang?" - itu bukan milikku; terima kasih kepada ♦, setiap pemungutan suara seperti itu oleh saya akan segera berlaku. Namun, saya tidak berpikir bahwa ini menjawab poin-poin kunci dari pertanyaan, yaitu tentang perilaku jalan buntu.
Marc Gravell
-2

Beberapa fakta penting tidak diberikan dalam jawaban lain:

"async await" lebih kompleks pada level CIL dan karenanya menghabiskan waktu memori dan CPU.

Tugas apa pun dapat dibatalkan jika waktu tunggu tidak dapat diterima.

Dalam kasus "async menanti" kami tidak memiliki penangan untuk tugas seperti itu untuk membatalkan atau memonitornya.

Menggunakan Task lebih fleksibel daripada "menunggu async".

Fungsi sinkronisasi apa pun dapat dengan dibungkus oleh async.

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 

Saya tidak mengerti mengapa saya harus hidup dengan duplikasi kode untuk sinkronisasi dan metode async atau menggunakan peretasan.

pengguna1785960
sumber