it-swarm.com.de

Wann sollte TaskCompletionSource <T> verwendet werden?

AFAIK, alles was sie weiß, ist, dass irgendwann ihre SetResult- oder SetException-Methode aufgerufen wird, um den Task<T> abzuschließen, der durch seine Task-Eigenschaft verfügbar gemacht wird.

Mit anderen Worten, er fungiert als Produzent für einen Task<TResult> und dessen Abschluss.

Ich habe hier das Beispiel gesehen:

Wenn ich einen Weg brauche, um eine Func asynchron auszuführen und eine Task diese Operation darstellen.

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

Welches könnte verwendet werden *, wenn ich nicht Task.Factory.StartNew - .__ hatte. Aber ich do habe Task.Factory.StartNew.

Frage:

Kann jemand bitte ein Szenario erklären, das direkt bis TaskCompletionSource.__ betrifft. und nicht zu einer hypothetischen Situation, in der ich keinen Task.Factory.StartNew habe?

167
Royi Namir

Ich verwende es meistens, wenn nur eine ereignisbasierte API verfügbar ist ( zum Beispiel 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;
}

Daher ist es besonders nützlich, wenn es zusammen mit dem Schlüsselwort c # 5 async verwendet wird.

201
GameScripting

Nach meinen Erfahrungen eignet sich TaskCompletionSource hervorragend zum Umwickeln alter asynchroner Muster in das moderne async/await-Muster.

Das nützlichste Beispiel, das mir einfällt, ist die Arbeit mit Socket. Es hat die alten APM- und EAP-Muster, nicht jedoch die awaitable Task-Methoden, die TcpListener und TcpClient haben. 

Ich persönlich habe einige Probleme mit der NetworkStream-Klasse und bevorzuge die rohe Socket. Da ich auch das async/await-Muster liebe, habe ich eine Erweiterungsklasse SocketExtender erstellt, die mehrere Erweiterungsmethoden für Socket erstellt.

Alle diese Methoden verwenden TaskCompletionSource<T>, um die asynchronen Aufrufe wie folgt umzuwandeln:

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

Ich übergebe die Variable socket in die Methode BeginAccept, sodass der Compiler einen geringfügigen Leistungsschub erhält, ohne den lokalen Parameter anheben zu müssen.

Dann die Schönheit von allem:

 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

Ein klassisches Szenario für die Verwendung von TaskCompletionSource ist für mich, wenn es möglich ist, dass meine Methode nicht notwendigerweise einen zeitaufwändigen Vorgang ausführen muss. Was es uns erlaubt, ist die Auswahl der speziellen Fälle, in denen wir einen neuen Thread verwenden möchten.

Ein gutes Beispiel dafür ist die Verwendung eines Cache. Sie können eine GetResourceAsync-Methode haben, die im Cache nach der angeforderten Ressource sucht und sofort (ohne Verwendung eines neuen Threads mithilfe von TaskCompletionSource) zurückgibt, wenn die Ressource gefunden wurde. Nur wenn die Ressource nicht gefunden wurde, möchten wir einen neuen Thread verwenden und ihn mit Task.Run() abrufen.

Ein Codebeispiel ist hier zu sehen: So führen Sie einen Code asynchron mit Tasks aus

35
Adi Lester

In diesem Blogbeitrag beschreibt Levi Botelho, wie Sie mit TaskCompletionSource einen asynchronen Wrapper für einen Prozess schreiben, sodass Sie ihn starten und auf seine Beendigung warten können.

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

und seine Verwendung

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

TaskCompletionSource wird zum Erstellen von Task Objekten verwendet, die keinen Code ausführen. _ In realen Szenarien TaskCompletionSource ist ideal für E/A-gebundene Operationen. Auf diese Weise erhalten Sie alle Vorteile von Aufgaben (z. B. Rückgabewerte, Fortsetzungen usw.), ohne einen Thread für die Dauer des Vorgangs zu blockieren. Wenn Ihre "Funktion" eine an IO gebundene Operation ist, wird es nicht empfohlen, einen Thread mit einer neuen Task zu blockieren. Verwenden Sie stattdessen TaskCompletionSource , um eine Slave-Task zu erstellen, die nur anzeigt, wann Ihre E/A-gebundene Operation beendet ist oder Fehler vorliegt.

11
v1p3r

Es sieht so aus, als ob niemand erwähnt wurde, aber ich denke, auch Unit-Tests können als real life betrachtet werden.

Ich finde, dass TaskCompletionSource nützlich ist, wenn eine Abhängigkeit mit einer asynchronen Methode verspottet wird.

Im aktuellen Programm, das getestet wird: 

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

In Unit-Tests:

// 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);
  // ...
}

Schließlich scheint diese Verwendung von TaskCompletionSource ein weiterer Fall von "einem Task-Objekt, das keinen Code ausführt".

8
superjos

Es gibt ein reales Beispiel mit einer anständigen Erklärung in diesem Beitrag aus dem Blog "Parallel Programming with .NET" . Sie sollten es wirklich lesen, aber hier ist trotzdem eine Zusammenfassung.

Der Blogbeitrag zeigt zwei Implementierungen für:

"Eine werkseitige Methode zum Erstellen" verzögerter "Aufgaben, die nicht geplant werden sollen Es wird tatsächlich geplant, bis ein vom Benutzer angegebenes Timeout aufgetreten ist.

Die erste gezeigte Implementierung basiert auf Task<> und weist zwei Hauptfehler auf. Im zweiten Implementierungsposten werden diese mithilfe von TaskCompletionSource<> abgeschwächt.

Hier ist die zweite Implementierung:

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

Ein reales Szenario, in dem ich TaskCompletionSource verwendet habe, ist beim Implementieren einer Download-Warteschlange. In meinem Fall, wenn der Benutzer 100 Downloads startet, möchte ich sie nicht alle gleichzeitig abfeuern, und anstatt eine angegebene Aufgabe zurückzugeben, gebe ich eine an TaskCompletionSource angehängte Aufgabe zurück. Sobald der Download abgeschlossen ist, schließt der in der Warteschlange befindliche Thread die Aufgabe ab. 

Das Schlüsselkonzept hier ist, dass ich mich entkoppele, wenn ein Client nach dem Start einer Aufgabe fragt, wenn sie tatsächlich gestartet wird. In diesem Fall, weil ich nicht möchte, dass sich der Client mit Ressourcenverwaltung befasst.

beachten Sie, dass Sie async/await in .net 4 verwenden können, solange Sie einen C # 5-Compiler (VS 2012+) verwenden. Weitere Informationen finden Sie in here .

3
Yaur

Dies kann zu stark vereinfacht werden, aber die TaskCompletion-Quelle ermöglicht es, auf ein Ereignis zu warten. Da das tcs.SetResult nur festgelegt wird, wenn das Ereignis eintritt, kann der Anrufer auf die Aufgabe warten.

In diesem Video erhalten Sie weitere Einblicke:

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

3
nmishr

Ich habe TaskCompletionSource verwendet, um eine Aufgabe auszuführen, bis sie abgebrochen wird. In diesem Fall handelt es sich um einen ServiceBus-Abonnenten, den ich normalerweise ausführen möchte, solange die Anwendung ausgeführt wird.

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