Apakah ada setiap skenario di mana menulis metode seperti ini:
public async Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return await DoAnotherThingAsync();
}
alih-alih ini:
public Task<SomeResult> DoSomethingAsync()
{
// Some synchronous code might or might not be here... //
return DoAnotherThingAsync();
}
akan masuk akal?
Mengapa menggunakan return await
konstruk ketika Anda dapat langsung kembali Task<T>
dari DoAnotherThingAsync()
doa batin ?
Saya melihat kode dengan return await
di banyak tempat, saya pikir saya mungkin melewatkan sesuatu. Tapi sejauh yang saya mengerti, tidak menggunakan kata kunci async / menunggu dalam hal ini dan langsung mengembalikan Tugas akan setara secara fungsional. Mengapa menambahkan overhead tambahan dari await
lapisan tambahan ?
c#
.net
.net-4.5
async-await
TX_
sumber
sumber
Jawaban:
Ada satu kasus licik ketika
return
dalam metode normal danreturn await
dalamasync
metode berperilaku berbeda: ketika dikombinasikan denganusing
(atau, lebih umum, apa punreturn await
dalam satutry
blok).Pertimbangkan dua versi metode ini:
Metode pertama akan
Dispose()
denganFoo
objek segera setelahDoAnotherThingAsync()
kembali metode, yang kemungkinan jauh sebelum benar-benar selesai. Ini berarti versi pertama mungkin buggy (karenaFoo
dibuang terlalu cepat), sedangkan versi kedua akan berfungsi dengan baik.sumber
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Dispose()
kembalivoid
. Anda akan membutuhkan sesuatu sepertireturn foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });
. Tapi saya tidak tahu mengapa Anda melakukan itu ketika Anda dapat menggunakan opsi kedua.{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }
. Kasus penggunaannya cukup sederhana: jika Anda menggunakan .NET 4.0 (seperti kebanyakan), Anda masih dapat menulis kode async dengan cara ini yang akan berfungsi dengan baik dipanggil dari 4,5 aplikasi.Foo
hanya setelahTask
selesai kembali , yang saya tidak suka, karena tidak perlu memperkenalkan concurrency.Jika Anda tidak perlu
async
(yaitu, Anda dapat mengembalikanTask
langsung), maka jangan gunakanasync
.Ada beberapa situasi di mana
return await
berguna, seperti jika Anda memiliki dua operasi asinkron untuk dilakukan:Untuk lebih lanjut tentang
async
kinerja, lihat artikel MSDN dan video Stephen Toub tentang topik ini.Pembaruan: Saya telah menulis posting blog yang lebih detail.
sumber
await
ini berguna dalam kasus kedua? Kenapa tidakreturn SecondAwait(intermediate);
?return SecondAwait(intermediate);
mencapai tujuan dalam kasus itu juga? Saya pikirreturn await
juga berlebihan di sini ...await
di baris pertama, Anda harus menggunakannya di baris kedua juga.async
versi itu akan menggunakan lebih sedikit sumber daya (utas) daripada versi sinkron Anda.SecondAwait
adalah `string, pesan galatnya adalah:" CS4016: Karena ini adalah metode async, ekspresi kembalian harus dari tipe 'string' daripada 'Task <string>' ".Satu-satunya alasan Anda ingin melakukannya adalah jika ada yang lain
await
dalam kode sebelumnya, atau jika Anda dengan cara tertentu memanipulasi hasilnya sebelum mengembalikannya. Cara lain yang mungkin terjadi adalah melaluitry/catch
perubahan yang mengubah cara pengecualian ditangani. Jika Anda tidak melakukan semua itu maka Anda benar, tidak ada alasan untuk menambahkan biaya tambahan untuk membuat metodeasync
.sumber
return await
perlu (alih-alih hanya mengembalikan tugas pemanggilan anak) bahkan jika ada beberapa yang lain menunggu dalam kode sebelumnya . Bisakah Anda memberikan penjelasan?async
lalu bagaimana Anda menunggu tugas pertama? Anda perlu menandai metode seolah-async
olah Anda ingin menggunakan apa pun yang menunggu. Jika metode ini ditandai sebagaiasync
dan Anda memilikiawait
kode sebelumnya, maka Anda perluawait
operasi async kedua agar jenisnya sesuai. Jika Anda baru saja dihapusawait
maka itu tidak akan dikompilasi karena nilai pengembalian tidak akan menjadi tipe yang tepat. Karena metode iniasync
hasilnya selalu dibungkus dengan tugas.async
metode Anda tidak mengembalikan tugas, Anda mengembalikan hasil tugas yang kemudian akan dibungkus.Task<Type>
secara eksplisit, sedangkanasync
perintah untuk kembaliType
(yang akan diubah oleh kompiler itu sendiriTask<Type>
).async
ini hanya gula sintaksis untuk melanjutkan secara eksplisit. Anda tidak perluasync
melakukan apa pun, tetapi ketika melakukan hampir semua operasi asinkron non-sepele itu secara dramatis lebih mudah untuk dikerjakan. Misalnya, kode yang Anda berikan sebenarnya tidak menyebarkan kesalahan seperti yang Anda inginkan, dan melakukannya dengan benar dalam situasi yang lebih kompleks mulai menjadi jauh lebih sulit. Meskipun Anda tidak pernah perluasync
, situasi yang saya jelaskan adalah di mana itu menambah nilai untuk menggunakannya.Kasus lain yang mungkin perlu Anda tunggu hasilnya adalah yang ini:
Dalam hal ini,
GetIFooAsync()
harus menunggu hasilGetFooAsync
karena jenisT
berbeda antara kedua metode danTask<Foo>
tidak langsung ditugaskanTask<IFoo>
. Tapi jika Anda menunggu hasilnya, itu hanya menjadiFoo
yang adalah langsung dialihkan keIFoo
. Kemudian metode async hanya mengemas kembali hasilnya di dalamTask<IFoo>
dan pergi Anda pergi.sumber
Task<>
tidak berubah-ubah.Membuat metode "thunk" async yang dinyatakan sederhana membuat mesin status async dalam memori sedangkan yang non-async tidak. Meskipun hal itu sering dapat mengarahkan orang untuk menggunakan versi non-async karena lebih efisien (yang benar) juga berarti bahwa jika terjadi hang, Anda tidak memiliki bukti bahwa metode tersebut terlibat dalam "tumpukan pengembalian / kelanjutan" yang terkadang membuatnya lebih sulit untuk memahami hang.
Jadi ya, ketika perf tidak kritis (dan biasanya tidak) saya akan melempar async pada semua metode thunk sehingga saya memiliki mesin negara async untuk membantu saya mendiagnosis hang nanti, dan juga untuk membantu memastikan bahwa jika mereka Metode thunk pernah berkembang seiring waktu, mereka pasti akan mengembalikan tugas yang salah alih-alih melempar.
sumber
Jika Anda tidak akan menggunakan pengembalian menunggu, Anda dapat merusak jejak tumpukan Anda saat debugging atau ketika itu dicetak dalam log pada pengecualian.
Ketika Anda mengembalikan tugas, metode memenuhi tujuannya dan itu keluar dari tumpukan panggilan. Saat Anda menggunakan
return await
Anda meninggalkannya di tumpukan panggilan.Sebagai contoh:
Tumpukan panggilan saat menggunakan menunggu: A menunggu tugas dari B => B menunggu tugas dari C
Tumpukan panggilan saat tidak menggunakan menunggu: A menunggu tugas dari C, yang telah dikembalikan B.
sumber
Ini juga membingungkan saya dan saya merasa bahwa jawaban sebelumnya mengabaikan pertanyaan Anda yang sebenarnya:
Yah kadang-kadang Anda benar - benar menginginkan
Task<SomeType>
, tetapi sebagian besar waktu Anda benar-benar menginginkan contohSomeType
, yaitu, hasil dari tugas.Dari kode Anda:
Seseorang yang tidak terbiasa dengan sintaks (saya, misalnya) mungkin berpikir bahwa metode ini harus mengembalikan a
Task<SomeResult>
, tetapi karena ditandai denganasync
, itu berarti bahwa jenis pengembalian yang sebenarnya adalahSomeResult
. Jika Anda hanya menggunakanreturn foo.DoAnotherThingAsync()
, Anda akan mengembalikan Tugas, yang tidak dapat dikompilasi. Cara yang benar adalah mengembalikan hasil tugas, jadireturn await
.sumber
var task = DoSomethingAsync();
akan memberi Anda tugas, bukanT
async/await
hal itu. Untuk pemahaman sayaTask task = DoSomethingAsync()
,, saatSomething something = await DoSomethingAsync()
keduanya bekerja. Yang pertama memberi Anda tugas yang tepat, sedangkan yang kedua, karenaawait
kata kunci, memberi Anda hasil dari tugas setelah selesai. Saya bisa, misalnya, milikiTask task = DoSomethingAsync(); Something something = await task;
.