2013-10-11 16 views
5

Ich habe eine Multithread-Anwendung, bei der ich jede Aufgabe nach einer bestimmten Zeit abbrechen muss, selbst wenn sie zum Zeitpunkt der Stornierung nicht verwaltete Ressourcen verwendet. Jetzt verwende ich den folgenden Code (z. B. eine Konsolenanwendung). In einer realen Anwendung kann die Verzögerung in der nicht verwalteten Ressource auftreten.Aufgabe nach Zeit abbrechen

static void Main() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      Task.Factory.StartNew(Do, TaskCreationOptions.LongRunning); 
     } 

     Console.ReadLine(); 
    } 

    private static void Do() 
    { 
     new Timer(Thread.CurrentThread.Abort, null, 1000, -1); 

     try 
     { 
      Console.WriteLine("Start " + Task.CurrentId); 
      Thread.Sleep(2000); 
      Console.WriteLine("End " + Task.CurrentId); 
     } 
     catch (Exception) 
     { 
      Console.WriteLine("Thread Aborted " + Task.CurrentId); 
     } 
    } 

Holen Sie sich das Ergebnis:

enter image description here

Aber ich bin mir nicht sicher, ob es sich für eine echte Anwendung aus der Sicht der Sicherheit richtig ist. habe ich auch CancellationToken in verschiedenen Varianten, aber es mir nicht korrektes Ergebnis geben, da, wo ich CancellAfter() oder .Delay() mit dem Zeitraum und storniert Aufgabe nach ceratain Zeit bekam ich folgende Ergebnisse:

static void Main() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      var clt = new CancellationTokenSource(); 

      Task task = new Task(() => 
      { 
       Task.Delay(2000).ContinueWith(_ => 
       { 
        clt.Cancel(); 

       }, clt.Token); 

       Do(clt.Token); 

      }, clt.Token); 

      task.Start(); 
     } 

     Console.ReadLine(); 
    } 

    private static void Do(CancellationToken cltToken) 
    { 
     Console.WriteLine("Start " + Task.CurrentId); 

     Thread.Sleep(2500); 

     if (!cltToken.IsCancellationRequested) 
     { 
      Console.WriteLine("End " + Task.CurrentId); 
     } 
     else 
     { 
      Console.WriteLine("Cancelled "+ Task.CurrentId); 
     } 
    } 

enter image description here

In dieser Situation müssen alle Aufgaben abgebrochen werden, da Thread.Sleep()> der Zeit zugewiesen ist, um jede Aufgabe auszuführen. Aber wir können sehen, dass ein Teil der Zeit ausgeführt wird.

ich auch folgende Konstruktion verwenden und das gleiche Ergebnis:

 static void Main() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      var clt = new CancellationTokenSource(); 
      clt.CancelAfter(2000); 

      Task.Factory.StartNew(Do, clt.Token); 

     } 

     Console.ReadLine(); 
    } 

    private static void Do(object obj) 
    { 
     var cltToken = (CancellationToken) obj; 

     Console.WriteLine("Start " + Task.CurrentId); 

     Thread.Sleep(2500); 

     if (!cltToken.IsCancellationRequested) 
     { 
      Console.WriteLine("End " + Task.CurrentId); 
     } 
     else 
     { 
      Console.WriteLine("Cancelled "+ Task.CurrentId); 
     } 
    } 

enter image description here

ich auch parallel nutzen und Stornierungs Token Innerhalb Methode Do() initialisieren, und verwenden Sie Timer nach Zeitspanne Token absagen, aber alle geben das gleiche Ergebnis.

SO, warum ist das passiert und was ist der richtige Weg, um die Aufgabe nach einer bestimmten Zeit abzubrechen ???

Antwort

5

Sie können die gleichen Ergebnisse wie Ihre ursprüngliche "abort" -Version erzielen, indem Sie die gleichen Timings verwenden. Zum Beispiel dieser Code:

static void Main() 
{ 
    var clt = new CancellationTokenSource(); 
    clt.CancelAfter(1000); 
    for (int i = 0; i < 10; i++) 
    { 
     Task.Run(() => Do(clt.Token)); 
    } 
    Console.ReadLine(); 
} 

private static void Do(CancellationToken cltToken) 
{ 
    Console.WriteLine("Start " + Task.CurrentId); 
    Thread.Sleep(2000); 

    if (!cltToken.IsCancellationRequested) 
    { 
     Console.WriteLine("End " + Task.CurrentId); 
    } 
    else 
    { 
     Console.WriteLine("Cancelled "+ Task.CurrentId); 
    } 
} 

Wird etwas simliar produzieren:

Start 111 
Start 112 
Start 113 
Start 114 
Start 115 
Start 116 
Start 117 
Start 118 
Start 119 
Start 120 
Cancelled 111 
Cancelled 112 
Cancelled 118 
Cancelled 116 
Cancelled 114 
Cancelled 113 
Cancelled 117 
Cancelled 115 
Cancelled 119 
Cancelled 120 

Mit einem CancellationTokenSource ist eine bessere Option als Threads abgebrochen wird. Thread.Abort ist eine schlechte Idee, da es den Thread abbricht, ohne ordnungsgemäße Bereinigungsmechanismen bereitzustellen. Mit dem Token können Sie kooperativ behandeln die Löschung in einer sauberen Art und Weise.

Warum Ihre anderen Optionen nicht richtig funktionierten - Die von Ihnen verwendeten Timings lagen etwas zu nah beieinander. Dies ist besonders dann ein Problem, wenn es unter dem Debugger ausgeführt wird, da es verhindert, dass die Timings (dh CancelAfter sowie Thread.Sleep) zur gleichen Zeit ausgelöst werden. Wenn Sie einen Release-Build außerhalb des Visual Studio-Hostprozesses ausführen, werden Sie wahrscheinlich feststellen, dass sie weitaus zuverlässiger arbeiten.

1

Zunächst ist das Problem, das Sie sehen, wo das Stornierungs-Token nicht signalisiert wird, wahrscheinlich auf subtile Timing-Variationen zurückzuführen. CancelAfter sollte gut funktionieren, aber Sie müssen den Unterschied zwischen dem Timeout und dem Schlaf erhöhen, um ein realistischeres Bild von dem zu bekommen, was passieren wird.

Zweitens, und ich kann der Vorbote der schlechten Nachrichten hier sein, aber wenn diese nicht verwaltete Ressource keine Mechanismen bietet, um eine Operation ordnungsgemäß zu beenden, dann ist das einfach exponentiell härter geworden. Die Gründe sind:

  • Offensichtlich gibt es keine Möglichkeit, die CancellationToken abzufragen, während ein Thread unmanaged Code ausführt. Es gibt also keine Möglichkeit, ein ordnungsgemäßes Herunterfahren selbst zu initiieren.
  • Abgesehen von der Tatsache, dass Sie einen Thread sowieso nicht abbrechen sollten, wird der Thread.Abort Aufruf das Abbruchsignal nicht in das Ziel injizieren, bis es dem verwalteten Bereich wieder beitritt. Mit anderen Worten: Abbrüche beenden keine Threads, die nicht verwalteten Code ausführen. Dies wurde absichtlich getan, um den Abbruch sicherer zu machen.
  • Die einzige Möglichkeit, dies zuverlässig zu erreichen, besteht darin, die nicht verwaltete Ressource out-of-process auszuführen. Das bedeutet, dass Sie einen neuen Prozess hochfahren müssen, um den nicht verwalteten Code auszuführen und WCF (oder ein anderes Kommunikationsprotokoll) zu verwenden, um Nachrichten/Daten hin und her zu senden. Wenn die nicht verwaltete Ressource nicht rechtzeitig reagiert, können Sie den Prozess beenden. Dies ist sicher, da das Töten eines anderen Prozesses den Status des aktuellen Prozesses nicht beschädigt.

    Hoffentlich hat jede unmanaged Ressource, die Sie verwenden, eine API mit einem anmutigen Beendigungsmechanismus. Wenn es gut geschrieben ist, könnte es eine solche Funktion haben, aber meine Erfahrung hat gezeigt, dass viele nicht.

    Verwandte Themen