2014-09-05 3 views
5

ich dieses Beispiel-Code haben:ContinueWith Verkettung funktioniert nicht wie erwartet

static void Main(string[] args) { 
     var t1 = Task.Run(async() => { 
      Console.WriteLine("Putting in fake processing 1."); 
      await Task.Delay(300); 
      Console.WriteLine("Fake processing finished 1. "); 
     }); 
     var t2 = t1.ContinueWith(async (c) => { 
      Console.WriteLine("Putting in fake processing 2."); 
      await Task.Delay(200); 
      Console.WriteLine("Fake processing finished 2."); 
     }); 
     var t3 = t2.ContinueWith(async (c) => { 
      Console.WriteLine("Putting in fake processing 3."); 
      await Task.Delay(100); 
      Console.WriteLine("Fake processing finished 3."); 
     }); 
     Console.ReadLine(); 
    } 

Die Konsolenausgabe verwirrt mich:

  • in gefälschten Verarbeitung Putting 1.
  • Gefälschte Verarbeitung beendet 1.
  • Putting in gefälschte Verarbeitung 2.
  • Putting in gefälschte Verarbeitung 3.
  • Fake-Verarbeitung fertig 3.
  • Fake-Verarbeitung 2. fertig

Ich bin an die Kette zu versuchen, die Aufgaben, so dass sie eine nach der anderen ausführen, was mache ich falsch? Und ich kann nicht erwarten, das ist nur Beispielcode, in Wirklichkeit stehe ich eingehende Aufgaben (einige asynchron, manche nicht) und möchte sie in der gleichen Reihenfolge ausführen, in der sie kamen, aber ohne Parallelität, ContinueWith schien besser als Erstellen eine ConcurrentQueue und Handhabung everything selbst, aber es funktioniert einfach nicht ...

+0

Die Warteanweisung "spaltet" die Aufgabe. Also bezieht sich t1 nur auf die erste Hälfte. Ich bin überrascht, dass eine gefälschte Verarbeitung 2 nicht unmittelbar nach dem Einfügen einer gefälschten Verarbeitung erfolgt. 1 –

+0

@WeylandYutani Ich erkläre in meiner Antwort, warum das nicht passiert. – Servy

+0

eigentlich spreche ich wieder Müll, also ignoriere mich –

Antwort

8

Werfen Sie einen Blick auf den Typ von t2. Es ist ein Task<Task>. t2 wird abgeschlossen , wenn es beendet, die Aufgabe zu starten, die die eigentliche Arbeit erledigt nicht, wenn diese Arbeit tatsächlich beendet.

Die kleinste Änderung an Ihrem Code, um es zum Arbeiten zu bringen wäre, eine unwrap nach Ihrem zweiten und dritten Aufruf zu ContinueWith hinzuzufügen, so dass Sie die Aufgabe, die den Abschluss Ihrer Arbeit darstellt, herausholen.

Die idiomatische Lösung wäre, die ContinueWith Anrufe vollständig zu entfernen und einfach await verwenden, um Aufgaben zu erweitern.

Interessanterweise würden Sie das gleiche Verhalten für t1 sehen, wenn Sie Task.Factory.StartNew verwendet, aber Task.Run ist speziell mit async lambdas arbeiten entworfen und tatsächlich intern auspackt alle Action<Task> Delegierten das Ergebnis der Aufgabe zurückkehren zurück, anstatt eine Aufgabe Das bedeutet, dass diese Aufgabe gestartet wird, weshalb Sie diese Aufgabe nicht auspacken müssen.

+0

Danke, Unwrap funktioniert (kann nicht verwenden warten, da der echte Code keine lineare Liste ist, erhalte ich einen Strom von Aufgaben, die seriell ausgeführt werden müssen, damit sie in die Warteschlange gestellt werden müssen.)). –

+0

@MartinSykora Ja, Sie * könnten '' erwarten' 'anstelle von' ContinueWith' verwenden, obwohl es schwer ist, Ihnen zu zeigen, wie Sie die Besonderheiten Ihrer Situation kennen. Höchstwahrscheinlich können Sie einfach eine Async-Methode schreiben, die eine Aufgabe akzeptiert, sie wartet, andere Dinge erwartet und andere Methoden aufruft und dann eine Aufgabe zurückgibt. So gut wie jedes Mal, wenn Sie 'ContinueWith' für eine Aufgabe aufrufen, können Sie sie wahrscheinlich als" erwarten "bezeichnen und sie wird mit wenigen Ausnahmen schöner herauskommen. – Servy

2

in Wirklichkeit bin ich Warteschlangen eingehende Aufgaben (einige asynchrone, einige nicht) und wollen, dass sie in der gleichen Reihenfolge auszuführen, sie kamen in, aber ohne Parallelität

Sie wahrscheinlich TPL Datenfluss verwendet werden soll für Das. Genauer gesagt, ActionBlock.

var block = new ActionBlock<object>(async item => 
{ 
    // Handle synchronous item 
    var action = item as Action; 
    if (action != null) 
    action(); 

    // Handle asynchronous item 
    var func = item as Func<Task>; 
    if (func != null) 
    await func(); 
}); 

// To queue a synchronous item 
Action synchronous =() => Thread.Sleep(1000); 
block.Post(synchronous); 

// To queue an asynchronous item 
Func<Task> asynchronous = async() => { await Task.Delay(1000); }; 
blockPost(asynchronous); 
+0

Schön, das könnte genau das sein, was ich brauche, wenn es funktioniert, wäre definitiv eleganter :-) –

Verwandte Themen