'await' berfungsi, tetapi memanggil task.Result hang / deadlocks

126

Saya memiliki empat tes berikut dan yang terakhir hang saat saya menjalankannya. Mengapa ini terjadi:

[Test]
public void CheckOnceResultTest()
{
    Assert.IsTrue(CheckStatus().Result);
}

[Test]
public async void CheckOnceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceResultTest()
{
    Assert.IsTrue(CheckStatus().Result); // This hangs
    Assert.IsTrue(await CheckStatus());
}

private async Task<bool> CheckStatus()
{
    var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
    Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
    IRestResponse<DummyServiceStatus> response = await restResponse;
    return response.Data.SystemRunning;
}

Saya menggunakan metode ekstensi ini untuk restsharp RestClient :

public static class RestClientExt
{
    public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
    {
        var tcs = new TaskCompletionSource<IRestResponse<T>>();
        RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
        return tcs.Task;
    }
}
public class DummyServiceStatus
{
    public string Message { get; set; }
    public bool ValidVersion { get; set; }
    public bool SystemRunning { get; set; }
    public bool SkipPhrase { get; set; }
    public long Timestamp { get; set; }
}

Mengapa tes terakhir hang?

Johan Larsson
sumber
7
Anda harus menghindari mengembalikan void dari metode async. Ini hanya untuk kompatibilitas mundur dengan event handler yang ada, sebagian besar dalam kode antarmuka. Jika metode async Anda tidak mengembalikan apa pun, itu harus mengembalikan Task. Saya memiliki banyak masalah dengan MSTest dan tidak dapat mengembalikan tes async.
ghord
2
@ghord: MSTest tidak mendukung async voidmetode pengujian unit sama sekali; mereka tidak akan berhasil. Namun, NUnit melakukannya. Yang mengatakan, saya setuju dengan prinsip umum lebih memilih async Tasklebih async void.
Stephen Cleary
@StephenCleary Ya, meskipun diizinkan di beta VS2012, yang menyebabkan semua jenis masalah.
ghord

Jawaban:

88

Anda mengalami situasi kebuntuan standar yang saya jelaskan di blog saya dan dalam artikel MSDN : asyncmetode ini mencoba menjadwalkan kelanjutannya ke utas yang diblokir oleh panggilan ke Result.

Dalam hal ini, Anda SynchronizationContextadalah yang digunakan oleh NUnit untuk menjalankan async voidmetode pengujian. Saya akan mencoba menggunakan async Taskmetode pengujian sebagai gantinya.

Stephen Cleary
sumber
4
mengubah ke async Tugas berhasil, sekarang saya perlu membaca konten tautan Anda beberapa kali, ty pak.
Johan Larsson
@ MarioLopez: Solusinya adalah dengan menggunakan " asyncsepenuhnya" (seperti yang disebutkan dalam artikel MSDN saya). Dengan kata lain - seperti yang dinyatakan dalam judul postingan blog saya - "jangan blokir kode asinkron".
Stephen Cleary
1
@ StephenCleary bagaimana jika saya harus memanggil metode async di dalam konstruktor? Konstruktor tidak bisa asinkron.
Raikol Amaro
1
@StephenCleary Di hampir semua balasan Anda di SO dan di artikel Anda, yang pernah saya lihat Anda bicarakan adalah mengganti Wait()dengan membuat metode panggilan async. Tapi bagi saya, ini tampaknya mendorong masalah ke hulu. Pada titik tertentu, sesuatu harus dikelola secara sinkron. Bagaimana jika fungsi saya sengaja disinkronkan karena mengelola thread pekerja yang berjalan lama Task.Run()? Bagaimana saya menunggu sampai selesai tanpa jalan buntu di dalam tes NUnit saya?
void.pointer
1
@ void.pointer: At some point, something has to be managed synchronously.- tidak sama sekali. Untuk aplikasi UI, entrypoint bisa menjadi async voidpengendali kejadian. Untuk aplikasi server, entrypoint bisa berupa async Task<T>tindakan. Lebih baik menggunakan asynckeduanya untuk menghindari pemblokiran utas. Anda dapat membuat pengujian NUnit Anda sinkron atau asinkron; jika asinkron, jadikan sebagai async Taskgantinya async void. Jika sinkron, seharusnya tidak SynchronizationContextada jadi seharusnya tidak ada kebuntuan.
Stephen Cleary
223

Mendapatkan nilai melalui metode async:

var result = Task.Run(() => asyncGetValue()).Result;

Memanggil metode asinkron secara sinkron

Task.Run( () => asyncMethod()).Wait();

Tidak ada masalah kebuntuan yang akan terjadi karena penggunaan Task.Run.

Herman Schoenfeld
sumber
15
-1 untuk mendorong penggunaan async voidmetode pengujian unit dan menghapus jaminan thread yang sama yang disediakan oleh SynchronizationContextdari sistem yang diuji.
Stephen Cleary
68
@StephenCleary: tidak ada "pendorong" dari async void. Ini hanya menggunakan konstruksi c # yang valid untuk menyelesaikan masalah kebuntuan. Cuplikan di atas adalah solusi yang sangat diperlukan dan sederhana untuk masalah OP. Stackoverflow adalah tentang solusi untuk masalah, bukan promosi diri yang bertele-tele.
Herman Schoenfeld
81
@StephenCleary: Artikel Anda tidak benar-benar mengartikulasikan solusi (setidaknya tidak jelas) dan bahkan jika Anda memiliki solusi, Anda akan menggunakan konstruksi seperti itu secara tidak langsung. Solusi saya tidak secara eksplisit menggunakan konteks, lalu apa? Intinya adalah, milik saya bekerja dan itu satu baris. Tidak perlu dua posting blog dan ribuan kata untuk menyelesaikan masalah ini. CATATAN: Saya bahkan tidak menggunakan async void , jadi saya tidak benar-benar tahu apa yang Anda bicarakan .. Apakah Anda melihat "async void" di mana pun dalam jawaban saya yang ringkas dan tepat?
Herman Schoenfeld
15
@HermanSchoenfeld, jika Anda menambahkan alasannya ke caranya , saya yakin jawaban Anda akan banyak bermanfaat.
ironstone13
19
Saya tahu ini agak terlambat, tetapi Anda harus menggunakan .GetAwaiter().GetResult()bukannya .Resultsehingga setiap Exceptiontidak dibungkus.
Camilo Terevinto
15

Anda dapat menghindari kebuntuan menambah ConfigureAwait(false)baris ini:

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);

Saya telah menjelaskan jebakan ini di posting blog saya Pitfalls of async / await

Vladimir
sumber
9

Anda memblokir UI dengan menggunakan properti Task.Result. Dalam Dokumentasi MSDN mereka dengan jelas menyebutkan bahwa,

" Properti Hasil adalah properti pemblokiran. Jika Anda mencoba mengaksesnya sebelum tugasnya selesai, utas yang saat ini aktif diblokir hingga tugas selesai dan nilainya tersedia. Dalam kebanyakan kasus, Anda harus mengakses nilai dengan menggunakan Await atau menunggu alih-alih mengakses properti secara langsung. "

Solusi terbaik untuk skenario ini adalah menghapus await & async dari metode & hanya menggunakan Tugas tempat Anda mengembalikan hasil. Itu tidak akan mengacaukan urutan eksekusi Anda.

Kesatria Kegelapan
sumber
3

Jika Anda tidak mendapatkan callback atau kontrol berhenti, setelah memanggil fungsi async service / API, Anda harus mengonfigurasi Context untuk mengembalikan hasil pada konteks yang disebut sama.

Menggunakan TestAsync().ConfigureAwait(continueOnCapturedContext: false);

Anda akan menghadapi masalah ini hanya di aplikasi web, tetapi tidak di static void main.

Mayank Pandit
sumber
ConfigureAwaitmenghindari kebuntuan dalam skenario tertentu dengan tidak berjalan dalam konteks utas asli.
davidcarr