Satu perbedaan utama adalah dalam propagasi pengecualian. Pengecualian, dilemparkan dalam sebuah async Task
metode, akan disimpan dalam kembali Task
objek dan tetap aktif sampai tugas akan diamati melalui await task
, task.Wait()
, task.Result
atau task.GetAwaiter().GetResult()
. Hal ini diperbanyak dengan cara ini bahkan jika terlempar dari sinkron bagian dari async
metode.
Pertimbangkan kode berikut, di mana OneTestAsync
dan AnotherTestAsync
berperilaku 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 DoTestAsync
sangat 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 void
metode (sebagai lawan async Task
metode). Pengecualian yang dimunculkan di dalam async void
metode 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 async
metode untuk Task
metode 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), RunSynchronously
masih dapat dijalankan secara asinkron.
Perbedaan penting lain adalah bahwa
yang async
/ await
versi 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
/async
sama sekali :)