Saya telah melakukan Tes Unit ini dan saya tidak mengerti mengapa "menunggu Task.Delay ()" tidak menunggu!
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
Berikut ini adalah hasil Uji Unit:
Bagaimana mungkin "menunggu" tidak menunggu, dan utas utama berlanjut?
Task.Run()
setelah yang pertamaConsole.WriteLine
?Jawaban:
new Task(async () =>
Sebuah tugas tidak mengambil
Func<Task>
, tetapi sebuahAction
. Ini akan memanggil metode asinkron Anda dan mengharapkannya berakhir ketika kembali. Tapi ternyata tidak. Ini mengembalikan tugas. Tugas itu tidak ditunggu oleh tugas baru. Untuk tugas baru, pekerjaan dilakukan setelah metode dikembalikan.Anda harus menggunakan tugas yang sudah ada alih-alih membungkusnya dengan tugas baru:
sumber
Task.Run(async() => ... )
juga merupakan pilihanmyTask.Start();
akan menaikkanInvalidOperationException
myTask.Start()
akan menghasilkan pengecualian untuk alternatifnya dan harus dihapus saat menggunakanTask.Run(...)
. Tidak ada kesalahan dalam solusi Anda.Masalahnya adalah Anda menggunakan kelas non-generik
Task
, yang tidak dimaksudkan untuk membuahkan hasil. Jadi, ketika Anda membuatTask
instance lewat delegasi async:... delegasi diperlakukan sebagai
async void
. Sebuahasync void
bukanTask
, itu tidak bisa ditunggu, pengecualiannya tidak dapat ditangani, dan itu adalah sumber dari ribuan pertanyaan yang dibuat oleh programmer yang frustrasi di sini di StackOverflow dan di tempat lain. Solusinya adalah menggunakanTask<TResult>
kelas generik , karena Anda ingin mengembalikan hasil, dan hasilnya adalah yang lainTask
. Jadi, Anda harus membuatTask<Task>
:Sekarang ketika Anda
Start
bagian luarTask<Task>
itu akan selesai hampir seketika karena tugasnya hanya untuk menciptakan bagian dalamTask
. Anda kemudian harus menunggu bagian dalamTask
juga. Ini adalah bagaimana hal itu dapat dilakukan:Anda memiliki dua alternatif. Jika Anda tidak membutuhkan referensi eksplisit ke bagian dalam
Task
maka Anda bisa menunggu bagian luarTask<Task>
dua kali:... atau Anda dapat menggunakan metode ekstensi bawaan
Unwrap
yang menggabungkan tugas-tugas luar dan dalam menjadi satu:Bukaan ini terjadi secara otomatis ketika Anda menggunakan metode yang jauh lebih populer
Task.Run
yang menciptakan tugas-tugas panas, sehingga metodeUnwrap
ini jarang digunakan saat ini.Jika Anda memutuskan bahwa delegasi async Anda harus mengembalikan hasil, misalnya a
string
, maka Anda harus mendeklarasikanmyTask
variabel yang bertipeTask<Task<string>>
.Catatan: Saya tidak menganjurkan penggunaan
Task
konstruktor untuk membuat tugas-tugas dingin. Sebagai praktik yang umumnya disukai, untuk alasan saya tidak benar-benar tahu, tapi mungkin karena jarang digunakan sehingga berpotensi menangkap pengguna / pengelola / pengulas kode yang tidak sadar lainnya karena terkejut.Saran umum: Hati-hati setiap kali Anda memberikan delegasi async sebagai argumen untuk suatu metode. Metode ini idealnya mengharapkan
Func<Task>
argumen (artinya memahami delegasi async), atau setidaknyaFunc<T>
argumen (artinya setidaknya yang dihasilkanTask
tidak akan diabaikan). Dalam kasus yang disayangkan bahwa metode ini menerimaAction
, delegasi Anda akan diperlakukan sebagaiasync void
. Ini jarang yang Anda inginkan, jika pernah.sumber
sumber
await
tugas, penundaan tugas tidak akan benar-benar menunda tugas itu, bukan? Anda menghapus fungsionalitas.