Sunting: Pertanyaan ini sepertinya masalah yang sama, tetapi tidak memiliki tanggapan ...
Sunting: Dalam uji kasus 5 tugas tampaknya macet dalam WaitingForActivation
keadaan.
Saya telah menemukan beberapa perilaku aneh menggunakan System.Net.Http.HttpClient di .NET 4.5 - di mana "menunggu" hasil panggilan ke (misalnya) httpClient.GetAsync(...)
tidak akan pernah kembali.
Ini hanya terjadi dalam keadaan tertentu ketika menggunakan fungsionalitas bahasa async / await baru dan Tasks API - kode tersebut sepertinya selalu berfungsi bila hanya menggunakan kelanjutan.
Berikut ini beberapa kode yang mereproduksi masalah - masukkan ini ke dalam "proyek MVC 4 WebApi" baru di Visual Studio 11 untuk mengekspos titik akhir GET berikut:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
Setiap titik akhir di sini mengembalikan data yang sama (header respons dari stackoverflow.com) kecuali /api/test5
yang tidak pernah selesai.
Sudahkah saya menemukan bug di kelas HttpClient, atau apakah saya menyalahgunakan API dengan cara tertentu?
Kode untuk direproduksi:
public class BaseApiController : ApiController
{
/// <summary>
/// Retrieves data using continuations
/// </summary>
protected Task<string> Continuations_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
}
/// <summary>
/// Retrieves data using async/await
/// </summary>
protected async Task<string> AsyncAwait_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return result.Content.Headers.ToString();
}
}
public class Test1Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await Continuations_GetSomeDataAsync();
return data;
}
}
public class Test2Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = Continuations_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test3Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return Continuations_GetSomeDataAsync();
}
}
public class Test4Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await AsyncAwait_GetSomeDataAsync();
return data;
}
}
public class Test5Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = AsyncAwait_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test6Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return AsyncAwait_GetSomeDataAsync();
}
}
sumber
HttpClient.GetAsync(...)
?Jawaban:
Anda menyalahgunakan API.
Inilah situasinya: di ASP.NET, hanya satu utas yang dapat menangani permintaan sekaligus. Anda dapat melakukan beberapa pemrosesan paralel jika perlu (meminjam utas tambahan dari kumpulan utas), tetapi hanya satu utas yang memiliki konteks permintaan (utas tambahan tidak memiliki konteks permintaan).
Ini dikelola oleh ASP.NET
SynchronizationContext
.Secara default, ketika Anda
await
aTask
, metode dilanjutkan pada yang ditangkapSynchronizationContext
(atau ditangkapTaskScheduler
, jika tidak adaSynchronizationContext
). Biasanya, ini hanya yang Anda inginkan: aksi pengontrol asinkron akan menghasilkanawait
sesuatu, dan ketika dilanjutkan, ia melanjutkan dengan konteks permintaan.Jadi, inilah mengapa
test5
gagal:Test5Controller.Get
dijalankanAsyncAwait_GetSomeDataAsync
(dalam konteks permintaan ASP.NET).AsyncAwait_GetSomeDataAsync
dijalankanHttpClient.GetAsync
(dalam konteks permintaan ASP.NET).HttpClient.GetAsync
mengembalikan yang belum selesaiTask
.AsyncAwait_GetSomeDataAsync
menungguTask
; karena tidak lengkap,AsyncAwait_GetSomeDataAsync
mengembalikan yang belum selesaiTask
.Test5Controller.Get
memblokir utas saat ini sampaiTask
selesai.Task
dikembalikan olehHttpClient.GetAsync
selesai.AsyncAwait_GetSomeDataAsync
mencoba untuk melanjutkan dalam konteks permintaan ASP.NET. Namun, sudah ada utas dalam konteks itu: utas diblokirTest5Controller.Get
.Inilah mengapa yang lain berfungsi:
test1
,,test2
dantest3
):Continuations_GetSomeDataAsync
menjadwalkan kelanjutan ke kumpulan utas, di luar konteks permintaan ASP.NET. Ini memungkinkan pengembalianTask
denganContinuations_GetSomeDataAsync
menyelesaikan tanpa harus memasukkan kembali konteks permintaan.test4
Dantest6
): SejakTask
adalah ditunggu , permintaan ASP.NET benang tidak diblokir. Ini memungkinkanAsyncAwait_GetSomeDataAsync
untuk menggunakan konteks permintaan ASP.NET ketika siap untuk melanjutkan.Dan inilah praktik terbaiknya:
async
metode "perpustakaan" Anda , gunakanConfigureAwait(false)
sedapat mungkin. Dalam kasus Anda, ini akan berubahAsyncAwait_GetSomeDataAsync
menjadivar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
s; ituasync
semua jalan turun. Dengan kata lain, gunakanawait
sebagai gantiGetResult
(Task.Result
danTask.Wait
juga harus diganti denganawait
).Dengan begitu, Anda mendapatkan kedua manfaat: kelanjutan (sisa
AsyncAwait_GetSomeDataAsync
metode) dijalankan pada utas utas dasar yang tidak harus memasukkan konteks permintaan ASP.NET; dan controller itu sendiriasync
(yang tidak memblokir utas permintaan).Informasi lebih lanjut:
async
/await
intro , yang mencakup deskripsi singkat tentang bagaimanaTask
pelayan menggunakanSynchronizationContext
.SynchronizationContext
membatasi permintaan konteks untuk hanya satu thread pada suatu waktu.Pembaruan 2012-07-13: Memasukkan jawaban ini ke dalam posting blog .
sumber
SynchroniztaionContext
yang menjelaskan bahwa hanya ada satu utas dalam konteks untuk beberapa permintaan? Jika tidak, saya pikir harus ada.SynchronizationContext
memang menyediakan beberapa fungsi penting: itu mengalir konteks permintaan. Ini termasuk semua jenis barang mulai dari otentikasi hingga cookie hingga kultur. Jadi di ASP.NET, alih-alih menyinkronkan kembali ke UI, Anda menyinkronkan kembali ke konteks permintaan. Ini mungkin berubah segera: yang baruApiController
memang memilikiHttpRequestMessage
konteks sebagai properti - jadi mungkin tidak diperlukan untuk mengalir melalui konteksSynchronizationContext
- tapi saya belum tahu.Sunting: Umumnya mencoba untuk menghindari melakukan di bawah ini kecuali sebagai upaya terakhir untuk menghindari kebuntuan. Baca komentar pertama dari Stephen Cleary.
Perbaikan cepat dari sini . Alih-alih menulis:
Mencoba:
Atau jika Anda membutuhkan hasil:
Dari sumber (diedit agar sesuai dengan contoh di atas):
Bagi saya ini sepertinya pilihan yang bisa digunakan karena saya tidak memiliki opsi untuk membuatnya async (yang saya lebih suka).
Dari sumber:
sumber
async
kode pada ASP.NET, dan pada kenyataannya dapat menyebabkan masalah pada skala. BTW,ConfigureAwait
tidak "memecah perilaku async yang tepat" dalam skenario apa pun; itu persis apa yang harus Anda gunakan dalam kode perpustakaan.Avoid Exposing Synchronous Wrappers for Asynchronous Implementations
. Seluruh bagian posting menjelaskan beberapa cara berbeda untuk melakukannya jika Anda benar - benar perlu .Karena Anda menggunakan
.Result
atau.Wait
atauawait
ini akan berakhir menyebabkan kebuntuan dalam kode Anda.Anda dapat menggunakan
ConfigureAwait(false)
diasync
metode untuk mencegah kebuntuanseperti ini:
sumber
Kedua sekolah ini tidak termasuk di dalamnya.
Berikut adalah skenario di mana Anda hanya perlu menggunakannya
atau semacamnya
Saya memiliki tindakan MVC yang berada di bawah atribut transaksi basis data. Idenya adalah (mungkin) untuk memutar kembali semua yang dilakukan dalam tindakan jika terjadi kesalahan. Ini tidak memungkinkan pengalihan konteks, jika tidak, kemunduran atau komit transaksi akan gagal dengan sendirinya.
Perpustakaan yang saya butuhkan adalah async karena diharapkan untuk menjalankan async.
Satu-satunya pilihan. Jalankan sebagai panggilan sinkronisasi biasa.
Saya hanya mengatakan kepada masing-masing sendiri.
sumber
Saya akan menempatkan ini di sini lebih untuk kelengkapan daripada relevansi langsung ke OP. Saya menghabiskan hampir sehari men-debug
HttpClient
permintaan, bertanya-tanya mengapa saya tidak pernah mendapatkan balasan.Akhirnya menemukan bahwa aku lupa untuk
await
paraasync
panggilan lebih bawah panggilan stack.Terasa seperti kehilangan titik koma.
sumber
Saya mencari di sini:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
Dan di sini:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx
Dan melihat:
Mengingat
await
versi ini berfungsi, dan apakah cara yang 'benar' dalam melakukan sesuatu, apakah Anda benar-benar membutuhkan jawaban untuk pertanyaan ini?Pilihan saya adalah: Menyalahgunakan API .
sumber
Test5Controller.Get()
menolak untuk menghilangkan penunggu dengan yang berikut:var task = AsyncAwait_GetSomeDataAsync(); return task.Result;
Perilaku yang sama dapat diamati.