2017-12-05 5 views
-1

Ich versuche, ein einfaches Programm zum besseren Verständnis TPL Dataflow zu machen. Ich versuche, eine lange laufende Aufgabe zu erstellen, die einen Datenflussblock neu startet, wenn er abgeschlossen ist.ActionBlock wird nie bei Fehlerzustand abgeschlossen

Meine aktuelle RestartActionBlock Task ist in der Lage, auf die Fertigstellung der ActionBlock zu warten, wenn ich auf dem Block explizit durch Eingabe von "-1" den Aufruf abschließe. Aber wenn ich versuche, eine Ausnahme auszulösen, um den Block zu blockieren oder die Blöcke Fault() - Interface-Methode aufzurufen, wird die Completion-Task ActionBlock niemals abgeschlossen. In dieser Situation wird der await singleTestFlow.Completion; Anruf nie fortgesetzt.

Nach dem Auslösen der Ausnahme oder dem Aufrufen der Fault() -Methode kann ich sicher sein, dass der Block im fehlerhaften Zustand ist, indem versucht wird, eine andere Eingabe in das Programm einzugeben und den Code zu prüfen, um festzustellen, ob der Block fehlerhaft ist :

ActionBlock in the faulted state

Wenn der Block in dem Fehlerzustand ist, warum zurückgekommen die await singleTestFlow.Completion; nie?

class Program 
{ 
    private static ActionBlock<string> singleTestFlow; 

    static void Main(string[] args) 
    { 
     //start thread that should restart a completed action block 
     Task.Run(RestartActionBlock); 

     Console.WriteLine("Enter -0 to exit, -1 to complete flow, -0- to throw an exception, Anything else otherwise"); 
     var input = Console.ReadLine(); 

     //allow user to input text until "-0" is entered 
     while (!input.Equals("-0")) 
     { 
      if (input.Equals("-1")) 
      { 
       singleTestFlow.Complete(); 
      } 

      singleTestFlow.Post(input); 

      input = Console.ReadLine(); 
     } 

     async Task RestartActionBlock() 
     { 
      var iterations = 0; 
      while (true) 
      { 
       singleTestFlow = new ActionBlock<string>(s => 
       { 
        if (s.Equals("-0-")) 
        { 
         //throw new Exception("Something went wrong in here"); 
         ((IDataflowBlock)singleTestFlow).Fault(new Exception("Something went wrong in here")); 
        } 
        Console.WriteLine($"{iterations}: " + s); 

       }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }); 

       await singleTestFlow.Completion; 

       var completionTask = singleTestFlow.Completion; 

       var message = $"action block: {iterations} "; 
       switch (completionTask.Status) 
       { 
        case TaskStatus.RanToCompletion: 
         message += "ran to completion"; 
         break; 
        case TaskStatus.Canceled: 
         message += "was canceled"; 
         break; 
        case TaskStatus.Faulted: 
         message += "has faulted"; 
         Console.WriteLine(completionTask.Exception); 
         break; 
       } 
       Console.WriteLine(message); 
       iterations++; 
      } 
     } 
    } 


} 

Sample Console Entry Der Punkt in der Konsole, wo i "eee" eingetragen ist, wo der Screenshot bemängelt Block Debug genommen wurde.

Antwort

2

Hier ist die einfache Lösung:

ersetzen
await singleTestFlow.Completion; // Throws if Completion is faulted, breaking your loop. 

mit

await Task.WhenAny(singleTestFlow.Completion); 

oder

await singleTestFlow.Completion.ContinueWith(_ => { }); 

Was die oben tut, ist vermeiden propagiert die Task-Ausnahme Completion. Dadurch kann Ihre RestartActionBlock Schleife für immer ausgeführt werden, wie Sie es beabsichtigt haben (im Gegensatz zum Löschen direkt nach Ihren Blockfehlern).

Idealerweise aber Sie sollten nicht die Rückgabewerte der beiden folgenden Anweisungen werden ohne Berücksichtigung:

Task.Run(RestartActionBlock); // Unobserved Task. Usually a bad idea. 

und

singleTestFlow.Post(input); 

Wenn Sie die Rückgabewerte der oben beobachteten, Sie‘ d tatsächlich bemerken, dass sie beginnen, um Hilfe zu schreien, sobald Ihre ActionBlock<string> Fehler.

Die von Task.Run(RestartActionBlock) Übergänge zu Faulted Zustand zurück Aufgabe sobald await singleTestFlow.Completion wirft - aber man kann nie benachrichtigt werden, weil Sie Aufgabe keinen Verweis halten an den oder seinen Status überprüfen.

In ähnlicher Weise, direkt nach Ihrer ActionBlock<string> Fehler, nachfolgende Aufrufe an singleTestFlow.Post(input) tatsächlich zurück false, was bedeutet, dass keine weiteren Elemente auf den Block gebucht werden. Sie werden einfach verworfen.

+0

Wow danke für die Hilfe! Es funktioniert jetzt. Von Ihrer Antwort habe ich zwei neue Fragen. Was ist der Unterschied zwischen dem Warten auf 'singleTestFlow.Completion' und' Task.WhenAny (singleTestFlow.Completion) '?Letzteres lässt meinen Code funktionieren, aber ich verstehe den Unterschied nicht. Zweitens, wie könnte ich die von 'Task.Run (RestartActionBlock) 'zurückgegebene Aufgabe verwenden? Ich möchte nicht auf die Fertigstellung warten, denn ich brauche den Rest des Codes in main weiter. – Slvrfn

+0

Wenn Sie Ihre 'RestartActionBlock' Aufgabe für immer laufen wollen, müssen Sie sicherstellen, dass es umfassende Ausnahmebehandlung eingebaut hat. Wenn' erwarten singleTestFlow.Completion' eine Ausnahme auslöst (aufgrund des Blockfehlers), 'RestartActionBlock' kehrt sofort zurück (ähnlich wie eine synchrone Methode würde). Es geht nie über das 'Warten' hinaus. Mit "Task.WhenAny" können Sie das umgehen, weil es lediglich einen Verweis auf die erste abgeschlossene (oder fehlerhafte) Aufgabe zurückgibt. Es wirft keine Ausnahmen zurück. Der Effekt ist ähnlich wie "try/catch" um "await singleTestFlow.Completion" zu setzen. –

+0

Hatten Sie einen Verweis auf die 'Task' gehalten zurück von' Task.Run (RestartActionBlock) ', würden Sie, dass irgendwann bemerken Übergänge es Zustand Faulted, und folgern, dass Ihre' RestartActionBlock' Schleife beendet haben müssen. Dies würde Ihnen beim Debuggen helfen. Fakt ist, dass ich persönlich das Problem mit einigem Erfolg diagnostizierte. –

Verwandte Themen