Apakah ada perbedaan konseptual antara dua bagian kode berikut:
async Task TestAsync()
{
await Task.Run(() => DoSomeWork());
}
dan
Task TestAsync()
{
return Task.Run(() => DoSomeWork());
}
Apakah kode yang dihasilkan juga berbeda?
EDIT: Untuk menghindari kebingungan dengan Task.Run
, kasus serupa:
async Task TestAsync()
{
await Task.Delay(1000);
}
dan
Task TestAsync()
{
return Task.Delay(1000);
}
PEMBARUAN TERLAMBAT: Selain jawaban yang diterima, ada juga perbedaan dalam cara LocalCallContext
penanganannya: CallContext.LogicalGetData dipulihkan meskipun tidak ada asinkron. Mengapa?
c#
async-await
avo
sumber
sumber
await
/async
sama sekali :)Jawaban:
Satu perbedaan utama adalah dalam propagasi pengecualian. Pengecualian, dilemparkan dalam sebuah
async Task
metode, akan disimpan dalam kembaliTask
objek dan tetap aktif sampai tugas akan diamati melaluiawait task
,task.Wait()
,task.Result
atautask.GetAwaiter().GetResult()
. Hal ini diperbanyak dengan cara ini bahkan jika terlempar dari sinkron bagian dariasync
metode.Pertimbangkan kode berikut, di mana
OneTestAsync
danAnotherTestAsync
berperilaku sangat berbeda:static async Task OneTestAsync(int n) { await Task.Delay(n); } static Task AnotherTestAsync(int n) { return Task.Delay(n); } // call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest static void DoTestAsync(Func<int, Task> whatTest, int n) { Task task = null; try { // start the task task = whatTest(n); // do some other stuff, // while the task is pending 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:Catatan, saya harus menekan Enteruntuk melihatnya.
Sekarang, jika saya panggil
DoTestAsync(AnotherTestAsync, -2)
, alur kerja kode di dalamnyaDoTestAsync
sangat berbeda, dan begitu juga hasilnya. Kali ini, saya tidak diminta untuk menekan Enter:Dalam kedua kasus,
Task.Delay(-2)
lempar di awal, sambil memvalidasi parameternya. Ini mungkin skenario yang dibuat-buat, tetapi secara teoriTask.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 lawanasync Task
metode). Pengecualian yang dimunculkan di dalamasync void
metode akan segera ditampilkan kembali pada konteks sinkronisasi thread saat ini (melaluiSynchronizationContext.Post
), jika thread saat ini memiliki satu (SynchronizationContext.Current != null)
. Jika tidak, akan ditampilkan kembali melaluiThreadPool.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 untukTask
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 async Task<int> MethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async 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(); // dead-lock here }
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 .
sumber
return Task.Run()
danawait Task.Run(); return
, bukanawait Task.Run().ConfigureAwait(false); return
Saya bingung dengan pertanyaan ini. Izinkan saya mencoba menjelaskan dengan menanggapi pertanyaan Anda dengan pertanyaan lain. Apa perbedaannya?
Func<int> MakeFunction() { Func<int> f = ()=>1; return ()=>f(); }
dan
Func<int> MakeFunction() { return ()=>1; }
?
Apapun perbedaan antara kedua hal saya, perbedaan yang sama adalah antara kedua hal Anda.
sumber
Task.Delay(1000).ContinueWith(() = {})
. Yang kedua, hanya sajaTask.Delay(1000)
. Perbedaannya agak halus, tetapi signifikan.Metode pertama bahkan tidak dapat dikompilasi.
Itu harus
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
Ada perbedaan konseptual utama antara keduanya. Yang pertama tidak sinkron, yang kedua tidak. Baca Performa Asinkron: Memahami Biaya Asinkron dan Menunggu untuk mendapatkan lebih banyak informasi tentang internal
async
/await
.Mereka menghasilkan kode yang berbeda.
.method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74 2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73 79 6e 63 3e 64 5f 5f 31 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x216c // Code size 62 (0x3e) .maxstack 2 .locals init ( [0] valuetype SOTestProject.Program/'<TestAsync>d__1', [1] class [mscorlib]System.Threading.Tasks.Task, [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ) IL_0000: ldloca.s 0 IL_0002: ldarg.0 IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'<TestAsync>d__1'::'<>4__this' IL_0008: ldloca.s 0 IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create() IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0014: ldloca.s 0 IL_0016: ldc.i4.m1 IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state' IL_001c: ldloca.s 0 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0023: stloc.2 IL_0024: ldloca.s 2 IL_0026: ldloca.s 0 IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&) IL_002d: ldloca.s 0 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task() IL_0039: stloc.1 IL_003a: br.s IL_003c IL_003c: ldloc.1 IL_003d: ret } // end of method Program::TestAsync
dan
.method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed { // Method begins at RVA 0x21d8 // Code size 23 (0x17) .maxstack 2 .locals init ( [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'<TestAsync2>b__4'() IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int) IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>) IL_0012: stloc.0 IL_0013: br.s IL_0015 IL_0015: ldloc.0 IL_0016: ret } // end of method Program::TestAsync2
sumber
Kedua contoh itu berbeda. Ketika sebuah metode ditandai dengan
async
kata kunci, kompilator menghasilkan mesin negara di belakang layar. Inilah yang bertanggung jawab untuk melanjutkan kelanjutan setelah menunggu ditunggu.Sebaliknya, ketika sebuah metode tidak ditandai dengan
async
Anda kehilangan kemampuan untukawait
menunggu. (Artinya, di dalam metode itu sendiri; metode tersebut masih bisa ditunggu oleh pemanggilnya.) Namun, dengan menghindariasync
kata kunci, Anda tidak lagi membuat mesin negara, yang dapat menambahkan sedikit overhead (mengangkat penduduk lokal ke bidang dari mesin negara, objek tambahan ke GC).Dalam contoh seperti ini, jika Anda dapat menghindari
async-await
dan mengembalikan awaitable secara langsung, ini harus dilakukan untuk meningkatkan efisiensi metode.Lihat pertanyaan ini dan jawaban ini yang sangat mirip dengan pertanyaan Anda dan jawaban ini.
sumber