async attendent les performances?

(Juste une question théorique - pour les applications à interface graphique)

En supposant que j'ai ce code avec beaucoup awaits:

public async Task<T> ConsumeAsync()
    {
          await A();
          await b();
          await c();
          await d();
          //..
    }

Où chaque tâche peut prendre très peu de temps,

Question (encore une fois , théorique)

Il peut être une situation où les globale du temps à s'occuper, avec tous ces "libération threads" et "chercher threads" ( rouge et vert ici :)

entrez la description de l'image ici

Prend plus de de temps qu'un seul thread qui pourrait faire tout le travail avec un peu de retard ,

Je veux dire, je voulais être le plus productif , mais à la place, puisque tous ces commutateurs d'avant en arrière - j'ai en fait perdu la productivité.

Un tel scénario peut-il se produire ?

25
demandé sur Royi Namir 2014-05-26 17:55:19

5 réponses

Un objet Task représente le résultat différé d'une opération en attente. Vous n'avez pas besoin d'utiliser tasks et async/await Si vous n'avez pas d'opérations en attente. Sinon, je crois async/await le code est généralement plus efficace que son analogue NU TPL ContinueWith.

Faisons un peu de timing:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        // async/await version
        static async Task<int> Test1Async(Task<int> task)
        {
            return await task;
        }

        // TPL version
        static Task<int> Test2Async(Task<int> task)
        {
            return task.ContinueWith(
                t => t.Result,
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default);
        }

        static void Tester(string name, Func<Task<int>, Task<int>> func)
        {
            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            for (int i = 0; i < 10000000; i++)
            {
                func(Task.FromResult(0)).Wait();
            }
            sw.Stop();
            Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
        }

        static void Main(string[] args)
        {
            Tester("Test1Async", Test1Async);
            Tester("Test2Async", Test2Async);
        }
    }
}

La sortie:

Test1Async: 1582ms
Test2Async: 4975ms

Ainsi, par défaut, await continuations sont traitées plus efficacement que ContinueWith continuations. Optimisons légèrement ce code:

// async/await version
static async Task<int> Test1Async(Task<int> task)
{
    if (task.IsCompleted)
        return task.Result;
    return await task;
}

// TPL version
static Task<int> Test2Async(Task<int> task)
{
    if (task.IsCompleted)
        return Task.FromResult(task.Result);

    return task.ContinueWith(
        t => t.Result,
        CancellationToken.None,
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default);
}

Le sortie:

Test1Async: 1557ms
Test2Async: 429ms

Maintenant, la version non asynchrone gagne. Dans le cas de la version async, je crois que cette optimisation a déjà été effectuée en interne par l'infrastructure async/await.

Quoi qu'il en soit, jusqu'à présent, nous n'avons traité que des tâches terminées (Task.FromResult). Introduisons l'asynchronie réelle (naturellement, nous ferons moins d'itérations cette fois):

static Task<int> DoAsync()
{
    var tcs = new TaskCompletionSource<int>();
    ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(0));
    return tcs.Task;
}

static void Tester(string name, Func<Task<int>, Task<int>> func)
{
    ThreadPool.SetMinThreads(200, 200);
    var sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        func(DoAsync()).Wait();
    }
    sw.Stop();
    Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}

La sortie:

Test1Async: 4207ms
Test2Async: 4734ms

Maintenant, la différence est très marginale, bien que la version async fonctionne encore légèrement mieux. Pourtant, Je pensez qu'un tel gain est vraiment négligeable, comparable au coût réel de l'opération asynchrone ou au coût de la restauration du contexte capturé pour when SynchronizationContext.Current != null.

L'essentiel est, si vous traitez des tâches asynchrones, optez pour async/await Si vous avez le choix, pas pour des raisons de performance mais pour la facilité d'utilisation, la lisibilité et la maintenabilité.

13
répondu noseratio 2014-05-27 03:30:51

Oui, en théorie. Normalement pas, dans le monde réel.

Dans le cas courant, async est utilisé pour les opérations liées aux E/S, et la surcharge de la gestion des threads est indétectable par rapport à eux. La plupart du temps, les opérations asynchrones prennent beaucoup de temps (par rapport à la gestion des threads) ou sont déjà terminées (par exemple, un cache). Notez que async a un "chemin rapide" qui débute si l'opération est déjà terminée, où il ne donne pas le thread.

Pour plus informations, voir le Zen de Async et Async Performance .

18
répondu Stephen Cleary 2014-05-26 14:03:46

Oui, ça peut arriver. N'oubliez pas non plus que - toute l'efficacité que vous pouvez programmer - le système de tâches a des frais généraux.

Si vous obtenez trop granulkar avec quelque chose comme ça, la surcharge de synchronisation peut vous tuer. Cela dit: les tâches sont programmées de manière assez efficace.

Mais l'ancienne règle colle: ne pas aller super granulaire. Parfois, l'optimisation aide.

2
répondu TomTom 2014-05-26 13:59:39

Un tel scénario peut-il se produire?

Absolument. Pour cette raison, vous devriez être consciencieux sur l'endroit où vous utilisez du code asynchrone. En règle générale, il est préférable de l'utiliser pour les méthodes qui effectueront une opération asynchrone (E/S disque ou réseau, par exemple). Le temps que prennent ces opérations l'emporte généralement sur le coût de la planification des tâches sur les threads. En outre, au niveau du système d'exploitation, ces types d'opérations sont intrinsèquement asynchrones, donc vous êtes réellement Suppression de une couche d'abstraction en utilisant des méthodes asynchrones.

Même dans ces cas, vous ne verrez probablement pas de différence de performance notable en passant au code asynchrone à moins que vous ne puissiez profiter de la concurrence. Par exemple, le code que vous avez posté ne verrait probablement aucun gain de performance réel à moins qu'il ne soit changé en quelque chose comme ceci:

await Task.WhenAll(new[]{A(), B(), C(), D(), ...});
2
répondu StriplingWarrior 2014-05-26 14:05:43

Oui, bien sûr que cela peut arriver. Avec tous les frais généraux de la création de la machine d'état, en cédant le contrôle et en utilisant IOCP threads. Mais comme dit, le TPL est assez optimisé. Par exemple, n'oublions pas que si votre TaskAwaitable se termine rapidement, il se peut qu'il n'y ait pas de surcharge et qu'il s'exécute de manière synchrone, ce qui peut arriver souvent avec des opérations rapides.

1
répondu Yuval Itzchakov 2014-05-26 14:03:14