web-dev-qa-db-fra.com

Quand faut-il utiliser TaskCompletionSource <T>?

Pour autant que je sache, tout ce qu’il sait, c’est qu’à un moment donné, sa méthode SetResult ou SetException est appelée pour compléter le Task<T> exposé par le biais de sa propriété Task.

En d'autres termes, il agit en tant que producteur pour un Task<TResult> et son achèvement.

J'ai vu ici l'exemple:

Si j'ai besoin d'un moyen d'exécuter un Func de manière asynchrone et d'avoir une tâche pour représenter cette opération.

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        {  
            T result = function(); 
            tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

Ce qui pourrait être utilisé * si je n’avais pas Task.Factory.StartNew - Mais j’ai do _ ai Task.Factory.StartNew.

Question:

Quelqu'un peut-il s'il vous plaît expliquer par exemple un scénario lié directement à TaskCompletionSource et pas dans une situation hypothétique dans laquelle je n'ai pas Task.Factory.StartNew?

167
Royi Namir

Je l'utilise surtout quand seule une API basée sur des événements est disponible ( par exemple windows phone 8 sockets ):

public Task<Args> SomeApiWrapper()
{
    TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>(); 

    var obj = new SomeApi();

    // will get raised, when the work is done
    obj.Done += (args) => 
    {
        // this will notify the caller 
        // of the SomeApiWrapper that 
        // the task just completed
        tcs.SetResult(args);
    }

    // start the work
    obj.Do();

    return tcs.Task;
}

C'est donc particulièrement utile lorsqu'il est utilisé avec le mot clé c # 5 async.

201
GameScripting

D'après mes expériences, TaskCompletionSource est idéal pour intégrer d'anciens modèles asynchrones au modèle async/await moderne.

L'exemple le plus bénéfique auquel je puisse penser est lorsque vous travaillez avec Socket. Il a les anciens modèles APM et EAP, mais pas les méthodes awaitable Task que TcpListener et TcpClient ont. 

Personnellement, j'ai plusieurs problèmes avec la classe NetworkStream et je préfère la valeur brute Socket. Étant donné que j'aime aussi le modèle async/await, j'ai créé une classe d'extension SocketExtender qui crée plusieurs méthodes d'extension pour Socket.

Toutes ces méthodes utilisent TaskCompletionSource<T> pour encapsuler les appels asynchrones comme suit:

    public static Task<Socket> AcceptAsync(this Socket socket)
    {
        if (socket == null)
            throw new ArgumentNullException("socket");

        var tcs = new TaskCompletionSource<Socket>();

        socket.BeginAccept(asyncResult =>
        {
            try
            {
                var s = asyncResult.AsyncState as Socket;
                var client = s.EndAccept(asyncResult);

                tcs.SetResult(client);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }

        }, socket);

        return tcs.Task;
    }

Je passe la socket dans les méthodes BeginAccept afin d'obtenir une légère amélioration des performances du compilateur sans avoir à lever le paramètre local.

Alors la beauté de tout ça:

 var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610));
 listener.Listen(10);

 var client = await listener.AcceptAsync();
67
Erik

Pour moi, un scénario classique d'utilisation de TaskCompletionSource est lorsqu'il est possible que ma méthode ne ne soit pas nécessairement obligée de faire une opération qui prend du temps. Cela nous permet de choisir les cas spécifiques dans lesquels nous aimerions utiliser un nouveau fil.

Un bon exemple pour cela est lorsque vous utilisez un cache. Vous pouvez avoir une méthode GetResourceAsync, qui recherche dans la mémoire cache la ressource demandée et renvoie immédiatement (sans utiliser de nouveau thread, en utilisant TaskCompletionSource) si la ressource a été trouvée. Seulement si la ressource n'a pas été trouvée, nous aimerions utiliser un nouveau thread et le récupérer à l'aide de Task.Run().

Un exemple de code peut être vu ici: Comment exécuter un code de façon conditionnelle en utilisant des tâches

35
Adi Lester

Dans cet article de blog , Levi Botelho explique comment utiliser la variable TaskCompletionSource pour écrire un wrapper asynchrone pour un processus de sorte que vous puissiez le lancer et attendre sa fin.

public static Task RunProcessAsync(string processPath)
{
    var tcs = new TaskCompletionSource<object>();
    var process = new Process
    {
        EnableRaisingEvents = true,
        StartInfo = new ProcessStartInfo(processPath)
        {
            RedirectStandardError = true,
            UseShellExecute = false
        }
    };
    process.Exited += (sender, args) =>
    {
        if (process.ExitCode != 0)
        {
            var errorMessage = process.StandardError.ReadToEnd();
            tcs.SetException(new InvalidOperationException("The process did not exit correctly. " +
                "The corresponding error message was: " + errorMessage));
        }
        else
        {
            tcs.SetResult(null);
        }
        process.Dispose();
    };
    process.Start();
    return tcs.Task;
}

et son utilisation

await RunProcessAsync("myexecutable.exe");
21
Sarin

TaskCompletionSource est utilisé pour créer Task des objets qui n'exécutent pas le code . Dans des scénarios réels/ TaskCompletionSource est idéal pour les opérations liées aux E/S. De cette manière, vous bénéficiez de tous les avantages des tâches (par exemple, renvoyer des valeurs, des continuations, etc.) sans bloquer un thread pour la durée de l'opération. Si votre "fonction" est une opération liée à IO, il n'est pas recommandé de bloquer un thread à l'aide d'un nouveau Task . À la place, utilisez TaskCompletionSource vous pouvez créer une tâche esclave pour indiquer simplement à quel moment votre opération liée aux E/S se termine ou se produit en cas d'erreur.

11
v1p3r

Il semble que personne ne l’ait mentionné, mais j’imagine que les tests unitaires peuvent aussi être considérés comme suffisants la vraie vie.

Je trouve que TaskCompletionSource est utile pour se moquer d'une dépendance avec une méthode asynchrone.

Dans le programme actuel sous test: 

public interface IEntityFacade
{
  Task<Entity> GetByIdAsync(string id);
}

En tests unitaires:

// set up mock dependency (here with NSubstitute)

TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>();

IEntityFacade entityFacade = Substitute.For<IEntityFacade>();

entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task);

// later on, in the "Act" phase

private void When_Task_Completes_Successfully()
{
  queryTaskDriver.SetResult(someExpectedEntity);
  // ...
}

private void When_Task_Gives_Error()
{
  queryTaskDriver.SetException(someExpectedException);
  // ...
}

Après tout, cette utilisation de TaskCompletionSource semble un autre cas "d'objet Task qui n'exécute pas de code".

8
superjos

Il y a un exemple du monde réel avec une explication décente dans cet article du blog "Parallel Programming with .NET" . Vous devriez vraiment le lire, mais voici un résumé quand même.

L'article de blog montre deux implémentations pour:

"Une méthode d'usine pour créer des tâches" retardées ", celles qui ne seront pas en fait programmées jusqu'à ce que le délai imparti par l'utilisateur soit écoulé."

La première implémentation illustrée est basée sur Task<> et présente deux défauts majeurs. La deuxième publication sur l'implémentation continue pour les atténuer en utilisant TaskCompletionSource<>.

Voici cette deuxième implémentation:

public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
    // Validate arguments
    if (millisecondsDelay < 0)
        throw new ArgumentOutOfRangeException("millisecondsDelay");
    if (action == null) throw new ArgumentNullException("action");

    // Create a trigger used to start the task
    var tcs = new TaskCompletionSource<object>();

    // Start a timer that will trigger it
    var timer = new Timer(
        _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);

    // Create and return a task that will be scheduled when the trigger fires.
    return tcs.Task.ContinueWith(_ =>
    {
        timer.Dispose();
        action();
    });
}
5
urig

Le scénario du monde réel où j’ai utilisé TaskCompletionSource s’applique lors de l’implémentation d’une file d’attente de téléchargement. Dans mon cas, si l'utilisateur démarre 100 téléchargements, je ne veux pas les lancer tous en même temps. Au lieu de renvoyer une tâche définie, je renvoie une tâche attachée à TaskCompletionSource. Une fois le téléchargement terminé, le fil d'exécution de la file d'attente termine la tâche. 

Le concept clé ici est que je découple lorsqu'un client demande qu'une tâche soit démarrée à partir du moment où elle démarre réellement. Dans ce cas, je ne veux pas que le client ait à gérer la gestion des ressources.

notez que vous pouvez utiliser async/wait dans .net 4 tant que vous utilisez un compilateur C # 5 (VS 2012+), voir ici pour plus de détails.

3
Yaur

C’est peut-être trop simpliste, mais la source TaskCompletion permet d’attendre un événement. Comme tcs.SetResult n'est défini que lorsque l'événement se produit, l'appelant peut attendre la tâche.

Regardez cette vidéo pour plus d'informations:

http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03-TipsForAsyncThreadsAndDatabinding

3
nmishr

J'ai utilisé TaskCompletionSource pour exécuter une tâche jusqu'à ce qu'elle soit annulée. Dans ce cas, c'est un abonné ServiceBus que je souhaite normalement exécuter tant que l'application est exécutée.

public async Task RunUntilCancellation(
    CancellationToken cancellationToken,
    Func<Task> onCancel)
{
    var doneReceiving = new TaskCompletionSource<bool>();

    cancellationToken.Register(
        async () =>
        {
            await onCancel();
            doneReceiving.SetResult(true); // Signal to quit message listener
        });

    await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}
0
Johan Gov