2013-10-11 10 views
5

Mam aplikację wielowątkową, w której muszę anulować każde zadanie po określonym czasie, nawet jeśli w momencie anulowania używają zasobów niezarządzanych. Teraz używam następującego kodu (na przykład aplikacji konsoli). W prawdziwej aplikacji opóźnienie może wystąpić w zasobach niezarządzanych.Anuluj zadanie według czasu

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

uzyskać wynik:

enter image description here

Ale nie jestem pewien, czy jest to dla rzeczywistej aplikacji z punktu widzenia bezpieczeństwa. użyłem również CancellationToken w różnych wariantach, ale to nie daje mi poprawny wynik, bo gdzie używam CancellAfter() lub .Delay() z przedziału czasu i anulować zadanie po ceratain razem mam następujące wyniki:

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

W tej sytuacji wszystkie zadania muszą zostać anulowane, ponieważ Thread.Sleep()> czasu przydzielonego do wykonania każdego zadania. Ale widzimy, że niektóre czasy do wykonania.

Używam również następującą konstrukcję i daje ten sam wynik:

 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

Używam również równoległa i zainicjować anulacji Reklamowe Wewnątrz metody do() i używać Timer cancell żeton po horyzont czasowy, ale wszystkie dają ten sam rezultat.

SO, Dlaczego tak się dzieje i jaki jest właściwy sposób anulowania zadania po określonym czasie ???

Odpowiedz

5

Możesz uzyskać takie same wyniki, jak w oryginalnej wersji "abort", używając tych samych ustawień czasowych. Na przykład, ten kod:

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

będzie produkować coś simliar do:

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 

używając CancellationTokenSource jest lepszym rozwiązaniem niż przerywanie wątków. Thread.Abort to zły pomysł, ponieważ przerywa wątek bez zapewnienia odpowiednich mechanizmów czyszczenia. Korzystanie z tokena pozwala na współpracująco obsługiwać anulowanie w czysty sposób.

Co do tego, dlaczego inne opcje nie działały prawidłowo - czasy, których używałeś, były zbyt blisko siebie. Jest to szczególnie problematyczne podczas działania w ramach debuggera, ponieważ uniemożliwi to jednoczesne uruchamianie taktowania (np. CancelAfter, jak również Thread.Sleep). Jeśli uruchomisz kompilację wydania poza procesem hosta programu Visual Studio, prawdopodobnie okaże się, że działa ona znacznie bardziej niezawodnie.

1

Po pierwsze, problem, który pojawia się, gdy token odwołania nie jest sygnalizowany, jest prawdopodobnie spowodowany subtelnymi różnicami w taktowaniu. CancelAfter powinien działać dobrze, ale będziesz musiał zwiększyć różnicę między czasem oczekiwania a snem, aby uzyskać bardziej realistyczny obraz tego, co się stanie.

Po drugie, i mogę być zwiastunem złych wiadomości tutaj, ale jeśli ten niezarządzany zasób nie oferuje mechanizmów dla wdzięcznego zakończenia operacji, to jest to po prostu wykładniczo trudniejsze. Powody są następujące:

  • Oczywiście nie ma możliwości odpytywania CancellationToken, gdy wątek wykonuje kod niezarządzany. Nie ma więc sposobu na samodzielne zamknięcie.
  • Oprócz faktu, że nie powinieneś przerwać nici, wywołanie Thread.Abort nie spowoduje wprowadzenia sygnału przerwania do celu, dopóki nie zostanie ponownie dołączony do dziedziny zarządzanej. Innymi słowy, przerywanie nie zakończy wątków, które wykonują niezarządzany kod. Dokonano tego celowo, aby abortować bezpieczniej.

Jedynym sposobem, aby to się stało niezawodnie, jest uruchomienie niezarządzanego zasobu poza procesem. Oznacza to, że będziesz musiał uruchomić nowy proces, aby wykonać niezarządzany kod, a następnie użyć WCF (lub innego protokołu komunikacyjnego) do wysyłania wiadomości/danych w jedną i drugą stronę. Jeśli zasób niezarządzany nie odpowiada w odpowiednim czasie, możesz zabić proces. Jest to bezpieczne, ponieważ zabicie innego procesu nie zakłóca stanu bieżącego procesu.

Mam nadzieję, że wszelkie niezarządzane zasoby, z których korzystasz, mają interfejs API z wbudowanym mechanizmem wdzięcznego zakończenia. Jeśli jest dobrze napisane, może mieć taką funkcję, ale moje doświadczenie pokazało, że wielu nie.