Apa perbedaan antara mengembalikan batal dan mengembalikan Tugas?

128

Dalam melihat berbagai sampel CTP Async C # saya melihat beberapa fungsi async yang kembali void , dan yang lain mengembalikan non-generik Task. Saya bisa melihat mengapa mengembalikan a Task<MyType>berguna untuk mengembalikan data ke pemanggil ketika operasi async selesai, tetapi fungsi yang saya lihat memiliki tipe Taskpengembalian tidak pernah mengembalikan data apa pun. Kenapa tidak kembali void?

James Cadd
sumber

Jawaban:

214

Jawaban Slaks dan Killercam baik; Saya pikir saya akan menambahkan sedikit lebih banyak konteks.

Pertanyaan pertama Anda pada dasarnya adalah tentang metode apa yang dapat ditandai async.

Metode yang ditandai sebagai asyncdapat kembali void, Taskatau Task<T>. Apa perbedaan di antara mereka?

Sebuah Task<T>metode async kembali dapat ditunggu, dan ketika tugas selesai akan mengajukan up T. sebuah

Sebuah Taskmetode async kembali dapat ditunggu, dan ketika selesai tugas, kelanjutan dari tugas dijadwalkan untuk berjalan.

SEBUAH void metode async kembali tidak dapat ditunggu; itu adalah metode "tembak dan lupakan". Itu bekerja secara tidak sinkron, dan Anda tidak memiliki cara untuk mengetahui kapan itu dilakukan. Ini lebih dari sedikit aneh; seperti kata SLaks, biasanya Anda hanya akan melakukan itu ketika membuat pengendali event asinkron. Acara menyala, pawang mengeksekusi; tidak ada yang akan "menunggu" tugas dikembalikan oleh pengendali acara karena penangan acara tidak mengembalikan tugas, dan bahkan jika mereka melakukannya, kode apa yang akan menggunakan Tugas untuk sesuatu? Biasanya bukan kode pengguna yang mentransfer kontrol ke handler sejak awal.

Pertanyaan kedua Anda, dalam komentar, pada dasarnya adalah tentang apa yang bisa awaitdiedit:

Metode apa yang bisa awaitdiedit? Apakah metode pengembalian yang dibatalkan dapat awaitdiedit?

Tidak, metode pengembalian batal tidak bisa ditunggu. Kompiler diterjemahkan await M()menjadi panggilan ke M().GetAwaiter(), di mana GetAwaitermungkin metode instance atau metode ekstensi. Nilai yang ditunggu harus berupa nilai yang bisa Anda dapatkan sebagai penunggu; jelas metode pengembalian-kembali tidak menghasilkan nilai dari mana Anda bisa mendapatkan penunggu.

Task-Mengembalikan metode dapat menghasilkan nilai yang bisa ditunggu. Kami mengantisipasi bahwa pihak ketiga akan ingin membuat implementasi sendiri Taskobjek-seperti yang dapat ditunggu, dan Anda akan dapat menunggu mereka. Namun, Anda tidak akan diizinkan untuk mendeklarasikan asyncmetode yang mengembalikan apa pun kecuali void, Taskatau Task<T>.

(PEMBARUAN: Kalimat terakhir saya mungkin dipalsukan oleh versi C # yang akan datang; ada proposal untuk memperbolehkan tipe yang dikembalikan selain tipe tugas untuk metode async.)

(PEMBARUAN: Fitur yang disebutkan di atas berhasil masuk ke C # 7.)

Eric Lippert
sumber
7
+1 Saya pikir satu-satunya hal yang hilang adalah perbedaan dalam bagaimana pengecualian diperlakukan dalam metode async yang tidak berlaku kembali.
João Angelo
10
@ JamesCadd: Misalkan beberapa pekerjaan asinkron melempar pengecualian. Siapa yang menangkapnya? Kode yang memulai tugas asinkron tidak lagi berada di stack - bahkan mungkin tidak berada di utas yang sama - dan pengecualian mengasumsikan bahwa semua blok catch / akhirnya ada di stack . Jadi apa yang kamu lakukan? Kami menyimpan informasi pengecualian di Tugas, sehingga Anda dapat memeriksanya nanti. Tetapi jika metode ini dibatalkan kembali maka tidak ada Tugas tersedia untuk kode pengguna. Bagaimana tepatnya kita menghadapi situasi itu telah menjadi masalah beberapa kontroversi dan saya tidak ingat pada saat ini apa yang kita putuskan.
Eric Lippert
8
Saya sebenarnya menanyakan pertanyaan Stephen Toub di BUILD. Di .NET 4.0 pengecualian yang tidak diobservasi, tidak ditangani dalam Tugas akhirnya akan menghentikan proses begitu TPL mendeteksi mereka tidak diamati. Di 4.5, mereka telah mengubah perilaku default sehingga pengecualian yang tidak teramati tetap akan dilaporkan melalui acara TaskScheduler :: UnobservedTaskException, tetapi tidak akan lagi menghentikan proses. Jika Anda ingin perilaku 4.0 lama, Anda dapat memilih kembali dengan <runtime> <ThrowUnobservedTaskExceptions enabled = "true" /> </runtime>. Kemungkinan besar perubahan itu dibuat justru untuk mendukung api dan lupakan metode batal async.
Drew Marsh
4
async voidmetode meningkatkan pengecualian mereka pada SynchronizationContextyang aktif pada saat mereka mulai mengeksekusi. Ini mirip dengan perilaku penangan acara (sinkron). @DrewMarsh: the UnobservedTaskExceptiondan runtime pengaturan hanya berlaku untuk "api dan lupa" async Task metode, tidak async voidmetode.
Stephen Cleary
1
Tautan kutipan untuk info penanganan pengecualian async: blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx#11
Luke Puplett
23

Dalam hal penelepon ingin menunggu tugas atau menambah kelanjutan.

Faktanya, satu-satunya alasan untuk kembali voidadalah jika Anda tidak dapat kembali Taskkarena Anda sedang menulis event handler.

Slaks
sumber
Saya pikir mungkin untuk menunggu metode yang mengembalikan tipe kosong juga - bisakah Anda sedikit menguraikan?
James Cadd
1
Tidak bisa. Jika metode ini kembali void, Anda tidak memiliki cara untuk mendapatkan tugas yang dihasilkannya. (Sebenarnya, saya tidak yakin apakah itu menghasilkan Tasksama sekali)
Slaks
18

Metode yang kembali Taskdan Task<T>dapat dikomposisikan - artinya Anda dapat awaitmelakukannya di dalam suatu asyncmetode.

asyncmetode pengembalian voidtidak dapat dikomposisikan, tetapi mereka memiliki dua sifat penting lainnya:

  1. Mereka dapat digunakan sebagai penangan acara.
  2. Mereka mewakili operasi asinkron "tingkat atas".

Poin kedua penting ketika Anda berurusan dengan konteks yang mempertahankan jumlah operasi asinkron yang luar biasa.

Konteks ASP.NET adalah salah satu konteks tersebut; jika Anda menggunakan Taskmetode async tanpa menunggu mereka dari voidmetode async , maka permintaan ASP.NET akan diselesaikan terlalu dini.

Konteks lain adalah AsyncContextsaya menulis untuk pengujian unit (tersedia di sini ) - AsyncContext.Runmetode melacak jumlah operasi yang beredar dan kembali ketika itu nol.

Stephen Cleary
sumber
12

Jenis Task<T>adalah jenis pekerja keras dari Perpustakaan Tugas Paralel (TPL), itu mewakili konsep "beberapa pekerjaan / pekerjaan yang akan menghasilkan hasil ketik Tdi masa depan". Konsep "pekerjaan yang akan selesai di masa depan tetapi tidak mengembalikan hasil" diwakili oleh jenis Tugas non-generik.

Tepatnya bagaimana hasil tipe Takan diproduksi dan detail implementasi dari tugas tertentu; pekerjaan mungkin dialihkan ke proses lain pada mesin lokal, ke utas lainnya, dll. Tugas TPL biasanya dialihkan ke utas pekerja dari kumpulan utas dalam proses saat ini, tetapi detail implementasi tidak mendasar pada Task<T>jenisnya; melainkan Task<T>dapat mewakili operasi latensi tinggi yang menghasilkan a T.

Berdasarkan komentar Anda di atas:

The awaitberarti ekspresi "mengevaluasi ungkapan ini untuk mendapatkan sebuah benda yang mewakili pekerjaan yang akan di masa depan menghasilkan hasil. Mendaftar sisa dari metode saat ini sebagai panggilan kembali terkait dengan kelanjutan tugas itu. Setelah tugas yang diproduksi dan kembali panggilan sudah terdaftar, segera kembalikan kontrol ke pemanggil saya ". Ini bertentangan / berbeda dengan pemanggilan metode biasa, yang berarti "ingat apa yang Anda lakukan, jalankan metode ini sampai benar-benar selesai dan kemudian mengambil di mana Anda tinggalkan, sekarang mengetahui hasil dari metode".


Sunting: Saya harus mengutip artikel Eric Lippert pada Oktober 2011 MSDN Magazine karena ini sangat membantu saya dalam memahami hal ini sejak awal.

Untuk informasi lebih lanjut dan whitepage lihat di sini .

Saya harap ini bisa membantu.

Ksatria bulan
sumber