Satu perbedaan utama adalah dalam propagasi pengecualian. Pengecualian, dilemparkan dalam sebuah async Taskmetode, akan disimpan dalam kembali Taskobjek dan tetap aktif sampai tugas akan diamati melalui await task, task.Wait(), task.Resultatau task.GetAwaiter().GetResult(). Hal ini diperbanyak dengan cara ini bahkan jika terlempar dari sinkron bagian dari asyncmetode.
Pertimbangkan kode berikut, di mana OneTestAsyncdan AnotherTestAsyncberperilaku sangat berbeda:
static async Task OneTestAsync(int n)
{
await Task.Delay(n);
}
static Task AnotherTestAsync(int n)
{
return Task.Delay(n);
}
static void DoTestAsync(Func<int, Task> whatTest, int n)
{
Task task = null;
try
{
task = whatTest(n);
Console.Write("Press enter to continue");
Console.ReadLine();
task.Wait();
}
catch (Exception ex)
{
Console.Write("Error: " + ex.Message);
}
}
Jika saya memanggil DoTestAsync(OneTestAsync, -2), itu menghasilkan keluaran berikut:
Tekan enter untuk melanjutkan
Error: Terjadi satu atau lebih error. Tunggu Task.Delay
Kesalahan: 2nd
Catatan, saya harus menekan Enteruntuk melihatnya.
Sekarang, jika saya panggil DoTestAsync(AnotherTestAsync, -2), alur kerja kode di dalamnya DoTestAsyncsangat berbeda, dan begitu juga hasilnya. Kali ini, saya tidak diminta untuk menekan Enter:
Kesalahan: Nilai harus -1 (menandakan waktu tunggu tak terbatas), 0 atau bilangan bulat positif.
Nama parameter: millisecondsDelayError: 1st
Dalam kedua kasus, Task.Delay(-2)lempar di awal, sambil memvalidasi parameternya. Ini mungkin skenario yang dibuat-buat, tetapi secara teori Task.Delay(1000)mungkin juga terjadi, misalnya, ketika API pengatur waktu sistem yang mendasarinya gagal.
Di samping catatan, logika penyebaran kesalahan masih berbeda untuk async voidmetode (sebagai lawan async Taskmetode). Pengecualian yang dimunculkan di dalam async voidmetode akan segera ditampilkan kembali pada konteks sinkronisasi thread saat ini (melalui SynchronizationContext.Post), jika thread saat ini memiliki satu ( SynchronizationContext.Current != null). Jika tidak, akan ditampilkan kembali melalui ThreadPool.QueueUserWorkItem). Penelepon tidak memiliki kesempatan untuk menangani pengecualian ini pada bingkai tumpukan yang sama.
Saya memposting beberapa detail lebih lanjut tentang perilaku penanganan pengecualian TPL di sini dan di sini .
T : Apakah mungkin meniru perilaku propagasi pengecualian dari asyncmetode untuk Taskmetode berbasis non-asinkron , sehingga metode berbasis asinkron tidak menampilkan bingkai tumpukan yang sama?
A : Jika memang dibutuhkan, maka ya, ada trik untuk itu:
async Task<int> MethodAsync(int arg)
{
if (arg < 0)
throw new ArgumentException("arg");
return 42 + arg;
}
Task<int> MethodAsync(int arg)
{
var task = new Task<int>(() =>
{
if (arg < 0)
throw new ArgumentException("arg");
return 42 + arg;
});
task.RunSynchronously(TaskScheduler.Default);
return task;
}
Namun perlu diperhatikan, dalam kondisi tertentu (seperti jika terlalu dalam di tumpukan), RunSynchronouslymasih dapat dijalankan secara asinkron.
Perbedaan penting lain adalah bahwa
yang async/ awaitversi lebih rentan terhadap mati-penguncian pada konteks sinkronisasi non-default . Misalnya, berikut ini akan dead-lock di aplikasi WinForms atau WPF:
static async Task TestAsync()
{
await Task.Delay(1000);
}
void Form_Load(object sender, EventArgs e)
{
TestAsync().Wait();
}
Ubah ke versi non-asinkron dan tidak akan mengunci:
Task TestAsync()
{
return Task.Delay(1000);
}
Sifat kunci buntu dijelaskan dengan baik oleh Stephen Cleary di blognya .
await/asyncsama sekali :)