'await' çalışır, ancak görev çağrılır. Sonuç kilitleniyor / kilitleniyor


126

Aşağıdaki dört testim var ve sonuncusu çalıştırdığımda takılıyor. Bu neden oluyor:

[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;
}

Restsharp RestClient için bu uzantı yöntemini kullanıyorum :

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; }
}

Son test neden takılıyor?


7
Zaman uyumsuz yöntemlerden void döndürmekten kaçınmalısınız. Yalnızca mevcut olay işleyicileriyle, çoğunlukla arabirim kodunda geriye dönük uyumluluk içindir. Zaman uyumsuz yönteminiz hiçbir şey döndürmezse, Task döndürmelidir. MSTest ile ilgili çok sayıda sorun yaşadım ve eşzamansız testleri geri döndürmeyi geçersiz kıldım.
ghord

2
@ghord: MSTest async voidbirim test yöntemlerini hiç desteklemiyor ; onlar sadece işe yaramayacak. Ancak, NUnit bunu yapar. Ben tercih genel prensibi ile kabul belirterek, async Tasküzerinde async void.
Stephen Cleary

@StephenCleary Evet, her türlü soruna neden olan VS2012 betalarında buna izin veriliyordu.
ghord

Yanıtlar:


88

Blogumda ve bir MSDN makalesinde anlattığım standart kilitlenme durumuyla karşılaşıyorsunuz : asyncyöntem, devamını, yapılan çağrı tarafından bloke edilen bir iş parçacığına programlamaya çalışıyor Result.

Bu durumda, test yöntemlerini SynchronizationContextyürütmek için NUnit tarafından kullanılan cihazınızdır async void. Bunun async Taskyerine test yöntemlerini kullanmayı denerdim .


4
eşzamansız olarak değişiyor Görev çalıştı, şimdi bağlantılarınızın içeriğini birkaç kez okumam gerekiyor, efendim.
Johan Larsson

@MarioLopez: Çözüm " asyncsonuna kadar" kullanmaktır (MSDN makalemde belirtildiği gibi). Diğer bir deyişle - blog gönderimin başlığında belirtildiği gibi - "eşzamansız kodu engelleme".
Stephen Cleary

1
@StephenCleary Bir yapıcı içinde zaman uyumsuz bir yöntemi çağırmam gerekirse ne olur? Oluşturucular eşzamansız olamaz.
Raikol Amaro

1
@StephenCleary SO'daki ve makalelerinizin hemen hemen tüm yanıtlarında, gördüğüm tek şey Wait()arama yöntemini yapmakla değiştiriyorsunuz async. Ama bana göre bu, sorunu yukarı doğru itiyor gibi görünüyor. Bir noktada, bir şeyin eşzamanlı olarak yönetilmesi gerekir. Ya işlevim uzun süredir çalışan iş parçacıklarını yönettiği için kasıtlı olarak eşzamanlıysa Task.Run()? NUnit testimde kilitlenme olmadan bitmesini nasıl beklerim?
void.pointer

1
@ void.pointer: At some point, something has to be managed synchronously.- hiç de değil. UI uygulamaları için, giriş noktası bir async voidolay işleyicisi olabilir. Sunucu uygulamaları için giriş noktası bir async Task<T>eylem olabilir. asyncİleti dizilerini engellemekten kaçınmak için her ikisinin de kullanılması tercih edilir . NUnit testinizin eşzamanlı veya eşzamansız olmasını sağlayabilirsiniz; zaman uyumsuzsa, async Taskyerine yapın async void. Eşzamanlıysa, SynchronizationContextbir kilitlenme olmaması için bir kilitlenme olmamalıdır.
Stephen Cleary

223

Eşzamansız bir yöntem aracılığıyla bir değer elde etme:

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

Zaman uyumsuz bir yöntemi eşzamanlı olarak çağırma

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

Task.Run kullanımı nedeniyle herhangi bir kilitlenme sorunu oluşmaz.


15
-1, async voidbirim test yöntemlerinin kullanımını teşvik etmek ve test SynchronizationContextedilen sistemden sağlanan aynı iş parçacığı garantilerini kaldırmak için.
Stephen Cleary

68
@StephenCleary: Eşzamansız boşluk "canlandırıcı" yoktur. Kilitlenme sorununu çözmek için yalnızca geçerli c # yapısını kullanıyor. Yukarıdaki kod parçası, OP'nin sorunu için vazgeçilmez ve basit bir çözümdür. Stackoverflow, kendini tanıtmakla değil, sorunlara çözümlerle ilgilidir.
Herman Schoenfeld

81
@StephenCleary: Makaleleriniz çözümü gerçekten ifade etmiyor (en azından net değil) ve bir çözümünüz olsa bile, bu tür yapıları dolaylı olarak kullanırsınız. Çözümüm açıkça bağlamları kullanmıyor, peki ne? Mesele şu ki, benimki işe yarıyor ve bu tek astarlı. Sorunu çözmek için iki blog yazısı ve binlerce kelimeye ihtiyaç yoktu. NOT: Eşzamansız boşluk bile kullanmıyorum , bu yüzden ne hakkında olduğunuzu gerçekten bilmiyorum .. Kısa ve doğru cevabımın herhangi bir yerinde "eşzamansız boşluk" görüyor musunuz?
Herman Schoenfeld

15
Eğer eklenirse, @HermanSchoenfeld neden karşı how , ben cevap çok fayda sağlayacağını düşünüyoruz.
ironstone13

19
Bu tür geç olduğunu biliyorum ama kullanıyor olmalıdır .GetAwaiter().GetResult()yerine .Resultherhangi böylece Exceptionsarılmış değil.
Camilo Terevinto

15

ConfigureAwait(false)Bu satıra kilitlenme eklemekten kaçınabilirsiniz :

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

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

Bu tuzağı blog gönderimde açıkladım async / await tuzakları


9

Task.Result özelliğini kullanarak kullanıcı arayüzünü engelliyorsunuz. Gelen MSDN Belgeler açıkça, belirtmiştik

" Sonuç özelliği bir engelleme özelliğidir. Görevi tamamlanmadan ona erişmeye çalışırsanız, görev tamamlanıncaya ve değer kullanılabilir olana kadar şu anda aktif olan iş parçacığı engellenir. Çoğu durumda, Await kullanarak değere erişmelisiniz. veya doğrudan mülke erişmek yerine bekleyin . "

Bu senaryo için en iyi çözüm olacaktır bekliyor ve uyumsuz hem kaldırmak yöntemleri ve sadece kullanmak Görevi işte sonucu getiriyorduk. Yürütme sıranızı bozmaz.


3

Herhangi bir geri arama almazsanız veya kontrol, hizmet / API zaman uyumsuz işlevini çağırdıktan sonra telefonu kapatırsa, aynı çağrılan bağlamda bir sonuç döndürmek için Bağlamı yapılandırmanız gerekir.

kullanım TestAsync().ConfigureAwait(continueOnCapturedContext: false);

Bu sorunla yalnızca web uygulamalarında karşılaşacaksınız, ancak içinde değil static void main.


ConfigureAwaitorijinal iş parçacığı bağlamında çalışmayarak belirli senaryolarda kilitlenmeyi önler.
davidcarr
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.