Saya memiliki aplikasi multi-tier .Net 4.5 yang memanggil metode menggunakan kata kunci baru C # async
dan await
yang baru saja hang dan saya tidak dapat melihat alasannya.
Di bagian bawah saya memiliki metode async yang memperluas utilitas database kami OurDBConn
(pada dasarnya pembungkus untuk objek DBConnection
dan yang mendasarinya DBCommand
):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Lalu saya memiliki metode asinkron tingkat menengah yang memanggil ini untuk mendapatkan beberapa total yang berjalan lambat:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Akhirnya saya memiliki metode UI (tindakan MVC) yang berjalan secara sinkron:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
Masalahnya adalah itu tergantung pada baris terakhir itu selamanya. Itu melakukan hal yang sama jika saya menelepon asyncTask.Wait()
. Jika saya menjalankan metode SQL lambat secara langsung dibutuhkan sekitar 4 detik.
Perilaku yang saya harapkan adalah ketika sampai asyncTask.Result
, jika belum selesai harus menunggu sampai selesai, dan setelah itu akan mengembalikan hasilnya.
Jika saya melangkah melalui debugger, pernyataan SQL selesai dan fungsi lambda selesai, tetapi return result;
baris GetTotalAsync
tidak pernah tercapai.
Tahu apa yang saya lakukan salah?
Adakah saran di mana saya perlu menyelidiki untuk memperbaikinya?
Mungkinkah ini menjadi jalan buntu di suatu tempat, dan jika demikian apakah ada cara langsung untuk menemukannya?
SynchronizationContext
.async
/await
kata kunci.Ini adalah
async
skenario jalan buntu campuran klasik , seperti yang saya jelaskan di blog saya . Jason menjelaskannya dengan baik: secara default, "konteks" disimpan di setiapawait
dan digunakan untuk melanjutkanasync
metode. "Konteks" ini adalah arusSynchronizationContext
kecuali itunull
, dalam hal ini adalah arusTaskScheduler
. Ketikaasync
metode mencoba untuk melanjutkan, pertama kali masuk kembali ke "konteks" yang diambil (dalam hal ini, ASP.NETSynchronizationContext
). ASP.NETSynchronizationContext
hanya mengizinkan satu utas dalam konteks pada satu waktu, dan sudah ada utas dalam konteks - utas diblokirTask.Result
.Ada dua pedoman yang akan menghindari kebuntuan ini:
async
sepenuhnya. Anda menyebutkan bahwa Anda "tidak dapat" melakukan ini, tetapi saya tidak yakin mengapa tidak. ASP.NET MVC di .NET 4.5 pasti dapat mendukungasync
tindakan, dan ini bukanlah perubahan yang sulit untuk dilakukan.ConfigureAwait(continueOnCapturedContext: false)
sebanyak mungkin. Ini mengesampingkan perilaku default untuk melanjutkan pada konteks yang diambil.sumber
ConfigureAwait(false)
menjamin bahwa fungsi saat ini dilanjutkan pada konteks yang berbeda?async
tindakan tanpa merusak cara kerjanya di sisi klien. Saya pasti berencana untuk menyelidiki opsi itu dalam jangka panjang.ConfigureAwait(false)
pohon panggilan akan menyelesaikan masalah OP.async
sekali tidak memengaruhi sisi klien. Saya menjelaskan ini di posting blog lain,async
Tidak Mengubah Protokol HTTP .async
"tumbuh" melalui basis kode. Jika metode pengontrol Anda mungkin bergantung pada operasi asinkron, maka metode kelas dasar harus dikembalikanTask<ActionResult>
. Mengalihkan proyek besar menjadiasync
selalu canggung karena mencampurasync
dan menyinkronkan kode itu sulit dan rumit.async
Kode murni jauh lebih sederhana.Saya berada dalam situasi kebuntuan yang sama tetapi dalam kasus saya memanggil metode asinkron dari metode sinkronisasi, yang berhasil untuk saya adalah:
apakah ini pendekatan yang bagus?
sumber
Hanya untuk menambah jawaban yang diterima (tidak cukup perwakilan untuk berkomentar), saya mengalami masalah ini muncul saat memblokir penggunaan
task.Result
, meskipun setiapawait
di bawahnyaConfigureAwait(false)
, seperti dalam contoh ini:Masalah sebenarnya terletak pada kode perpustakaan eksternal. Metode pustaka asinkron mencoba melanjutkan dalam konteks sinkronisasi panggilan, tidak peduli bagaimana saya mengonfigurasi menunggu, yang menyebabkan kebuntuan.
Jadi, jawabannya adalah menggulung versi saya sendiri dari kode perpustakaan eksternal
ExternalLibraryStringAsync
, sehingga itu akan memiliki properti lanjutan yang diinginkan.jawaban yang salah untuk tujuan sejarah
Setelah banyak kesakitan dan kesedihan, saya menemukan solusi yang terkubur dalam posting blog ini (Ctrl-f untuk 'deadlock'). Ini berputar di sekitar penggunaan
task.ContinueWith
, bukan telanjangtask.Result
.Contoh deadlock sebelumnya:
Hindari kebuntuan seperti ini:
sumber
Task
selesai, dan memberi pemanggil sarana untuk menentukan kapan mutasi objek yang dikembalikan benar-benar terjadi.GetFooSynchronous
metode?Task
alih - alih memblokir.jawaban cepat: ubah baris ini
untuk
Mengapa? Anda sebaiknya tidak menggunakan .result untuk mendapatkan hasil dari tugas di dalam sebagian besar aplikasi kecuali aplikasi konsol jika Anda melakukannya program Anda akan hang saat sampai di sana
Anda juga dapat mencoba kode di bawah ini jika Anda ingin menggunakan .Result
sumber