100 votes

Appel de méthodes asynchrones à partir de code non asynchrone

Je suis dans le processus de mise à jour d'une bibliothèque qui a une API de surface qui a été construit en .NET 3.5. En conséquence, toutes les méthodes sont synchrones. Je ne peux pas changer l'API (c'est à dire, de convertir les valeurs de retour à la Tâche) parce que cela exigerait que tous les appelants changement. Donc, je suis parti avec à la meilleure manière d'appel de méthodes asynchrones en mode synchrone. C'est dans le contexte de ASP.NET 4, ASP.NET de Base, et .NET/.NET de Base des applications console.

Je ne peut pas avoir été assez clair sur la situation, c'est que j'ai un code existant qui n'est pas asynchrone au courant, et je veux utiliser les nouvelles bibliothèques comme Système.Net.Http et le kit SDK AWS qui prennent en charge uniquement de méthodes asynchrones. J'ai donc besoin de combler l'écart, et être en mesure d'avoir un code qui peut être appelée de manière synchrone mais alors peut-appel de méthodes asynchrones ailleurs.

J'ai fait beaucoup de lecture, et il y a un certain nombre de fois ce qui a été demandé et répondu.

L'appel de méthode asynchrone de non méthode async

De manière synchrone en attente d'une opération asynchrone, et pourquoi a-t Wait() de geler le programme ici

L'appel d'une méthode asynchrone à partir d'une méthode synchrone

Comment puis-je exécuter un async Task<T> méthode synchrone?

L'appel de méthode asynchrone synchrone

Comment appeler la méthode asynchrone synchrone de méthode en C#?

Le problème est que la plupart des réponses sont différentes! L'approche la plus commune que j'ai vu est utiliser .Le résultat, mais cela peut impasse. J'ai essayé tous les suivants, et qu'ils travaillent, mais je ne suis pas sûr de ce qui est la meilleure approche pour éviter les blocages, ont de bonnes performances, et joue bien avec le moteur d'exécution (en termes d'honorer la tâche des planificateurs, des tâches des options de création, etc). Est-il une réponse définitive? Quelle est la meilleure approche?

private static T taskSyncRunner<T>(Func<Task<T>> task)
    {
        T result;
        // approach 1
        result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 2
        result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 3
        result = task().ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 4
        result = Task.Run(task).Result;

        // approach 5
        result = Task.Run(task).GetAwaiter().GetResult();


        // approach 6
        var t = task();
        t.RunSynchronously();
        result = t.Result;

        // approach 7
        var t1 = task();
        Task.WaitAll(t1);
        result = t1.Result;

        // approach 8?

        return result;
    }

118voto

usr Points 74796

Donc, je suis parti avec à la meilleure manière d'appel de méthodes asynchrones en mode synchrone.

Tout d'abord, c'est un OK de chose à faire. Je suis à l'indiquer, car il est commun sur un Débordement de Pile à ce point comme un acte du diable comme une déclaration générale, sans égard pour le cas concret.

Il n'est pas nécessaire d'être asynchrone tout le chemin de la rectitude. Bloque sur quelque chose asynchrone à faire la synchronisation a un coût que peut importe ou peut-être totalement hors de propos. Cela dépend du cas concret.

Les blocages proviennent de deux threads tentent d'entrer sur le même single-threaded contexte de synchronisation dans le même temps. Toute technique qui évite de cette manière fiable évite les blocages causés par le blocage.

Ici, tous vos appels à l' .ConfigureAwait(false) sont inutiles parce que vous n'êtes pas en attente.

RunSynchronously n'est pas valide à utiliser, parce que toutes les tâches ne peuvent être traitées de cette façon.

.GetAwaiter().GetResult() est différent de Result/Wait() en ce qu'elle imite l' await la propagation d'exception comportement. Vous devez décider si vous voulez ou pas. (Ainsi, la recherche de ce que le comportement est; pas besoin de le répéter ici.)

En outre, toutes ces approches ont des performances similaires. Ils vont allouer un OS événement d'une façon ou d'une autre et de les bloquer. C'est la partie coûteuse. Je ne sais pas quelle approche est absolument moins cher.

Personnellement, j'aime l' Task.Run(() => DoSomethingAsync()).Wait(); modèle parce qu'il évite les blocages, de manière catégorique, est simple et ne pas masquer certaines exceptions GetResult() pourraient se cacher. Mais vous pouvez utiliser GetResult() avec cette.

57voto

Stephen Cleary Points 91731

Je suis dans le processus de mise à jour d'une bibliothèque qui a une API de surface qui a été construit en .NET 3.5. En conséquence, toutes les méthodes sont synchrones. Je ne peux pas changer l'API (c'est à dire, de convertir les valeurs de retour à la Tâche) parce que cela exigerait que tous les appelants changement. Donc, je suis parti avec à la meilleure manière d'appel de méthodes asynchrones en mode synchrone.

Il n'est pas universelle "meilleure" façon d'effectuer la synchronisation-sur-async anti-modèle. Seulement une variété de hacks qui ont chacun leurs propres inconvénients.

Ce que je recommande, c'est que vous garder l'ancienne Api synchrone et puis d'introduire des Api asynchrones à côté d'eux. Vous pouvez le faire à l'aide de la "argument booléen hack" comme décrit dans mon article MSDN sur des friches industrielles Asynchrone.

Tout d'abord, une brève explication des problèmes avec chaque approche dans votre exemple:

  1. ConfigureAwait n'a de sens que lorsqu'il y a un await; sinon, il ne fait rien.
  2. Result s'occupera de l'emballage des exceptions dans un AggregateException; si vous devez bloquer, utilisez GetAwaiter().GetResult() à la place.
  3. Task.Run va exécuter son code sur un thread du pool (évidemment). C'est très bien, seulement si le code peut s'exécuter sur un thread du pool.
  4. RunSynchronously est une avancée de l'API utilisée dans de très rares situations lorsque vous effectuez dynamique basée sur les tâches de parallélisme. Vous n'êtes pas dans ce scénario.
  5. Task.WaitAll avec une seule tâche est la même que juste Wait().
  6. async () => await x est juste moins efficace façon de dire () => x.
  7. Le blocage d'une tâche a commencé à partir de l'actuel fil peuvent provoquer des blocages.

En voici le détail:

// Problems (1), (3), (6)
result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (3)
result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (7)
result = task().ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (2), (3)
result = Task.Run(task).Result;

// Problems (3)
result = Task.Run(task).GetAwaiter().GetResult();

// Problems (2), (4)
var t = task();
t.RunSynchronously();
result = t.Result;

// Problems (2), (5)
var t1 = task();
Task.WaitAll(t1);
result = t1.Result;

Au lieu de ces approches, puisque vous avez existant, de travail synchrone code, vous devez l'utiliser avec la plus récente naturellement-le code asynchrone. Par exemple, si votre code utilisé WebClient:

public string Get()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

et si vous souhaitez ajouter une API asynchrone, alors je voudrais faire comme ceci:

private async Task<string> GetCoreAsync(bool sync)
{
  using (var client = new WebClient())
  {
    return sync ?
        client.DownloadString(...) :
        await client.DownloadStringTaskAsync(...);
  }
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

ou, si vous devez utiliser HttpClient pour certaines raisons:

private string GetCoreSync()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

private static HttpClient HttpClient { get; } = ...;

private async Task<string> GetCoreAsync(bool sync)
{
  return sync ?
      GetCoreSync() :
      await HttpClient.GetString(...);
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

Avec cette approche, votre logique serait d'aller dans l' Core méthodes, qui peut être exécuté de manière synchrone ou asynchrone (tel que déterminé par l' sync paramètre). Si sync est true, puis le cœur méthodes doit retourner un déjà-tâche terminée. Pour la mise en oeuvre, l'utilisation synchrone Api pour exécuter de manière synchrone, et de l'utilisation des Api asynchrones pour exécuter de manière asynchrone.

Finalement, je recommande la dépréciation de l'Api synchrone.

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