143 votes

await" fonctionne, mais l'appel à task.Result se bloque.

J'ai les quatre tests suivants et le dernier se bloque lorsque je l'exécute. Pourquoi cela se produit-il ?

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

J'utilise cette méthode d'extension pour 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; }
}

Pourquoi le dernier test se bloque-t-il ?

256voto

Herman Schoenfeld Points 1480

Acquérir une valeur via une méthode asynchrone :

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

Appel synchrone d'une méthode asynchrone

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

Aucun problème de blocage ne se produira en raison de l'utilisation de Task.Run.

101voto

Stephen Cleary Points 91731

Vous vous retrouvez dans la situation de blocage standard que je décris. sur mon blog y dans un article de MSDN : le async tente de planifier sa continuation sur un thread qui est bloqué par l'appel à la méthode Result .

Dans ce cas, votre SynchronizationContext est celui utilisé par NUnit pour exécuter async void les méthodes d'essai. J'essaierais d'utiliser async Task les méthodes de test à la place.

18voto

Lenin Points 179

Vous pouvez éviter les blocages en ajoutant ConfigureAwait(false) à cette ligne :

IRestResponse<DummyServiceStatus> response = await restResponse;

\=>

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

J'ai décrit ce piège dans mon billet de blogue Les pièges d'async/await

9voto

Dark Knight Points 731

Vous bloquez l'interface utilisateur en utilisant la propriété Task.Result. Dans Documentation MSDN ils l'ont clairement mentionné,

" Le Résultat est une propriété bloquante. Si vous essayez d'y accéder avant que sa tâche ne soit terminée, le thread actuellement actif est bloqué jusqu'à ce que la tâche soit terminée et que la valeur soit disponible. Dans la plupart des cas cas, vous devez accéder à la valeur en utilisant Attendez o attendre au lieu de d'accéder directement à la propriété."

La meilleure solution pour ce scénario serait de supprimer await et async à partir de méthodes & utilisation uniquement Tâche où vous retournez le résultat. Cela ne perturbera pas votre séquence d'exécution.

3voto

Ogglas Points 1

Un complément à la réponse donnée par @HermanSchoenfeld. Malheureusement, la citation ci-dessous n'est pas vraie :

Aucun problème de blocage ne se produira en raison de l'utilisation de Task.Run.

public String GetSqlConnString(RubrikkUser user, RubrikkDb db) 
{ 
    // deadlock if called from threadpool, 
    // works fine on UI thread, works fine from console main 
    return Task.Run(() => 
        GetSqlConnStringAsync(user, db)).Result; 
}

L'exécution est enveloppée dans un Task.Run, qui planifiera la tâche sur le threadpool et bloquera le thread appelant. Ceci est correct, tant que le thread appelant n'est pas un thread du pool de threads. Si le thread appelant fait partie du pool de threads, le désastre suivant se produit : Une nouvelle tâche est mise en file d'attente à la fin de la file d'attente, et le thread du pool de threads qui exécuterait éventuellement la tâche est bloqué jusqu'à ce que la tâche soit exécutée.

Dans le code de bibliothèque, il n'y a pas de solution facile, car vous ne pouvez pas savoir dans quel contexte votre code est appelé. La meilleure solution est de n'appeler du code asynchrone que par du code asynchrone, de bloquer les API de synchronisation par des méthodes de synchronisation, de ne pas les mélanger.

Source :

https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X