2013-05-17 24 views
9

Muszę wykonać długą operację procesu w wątku i kontynuować, zwracając wynik do funkcji. Oto mój kod:C# limit czasu zadania fabrycznego

Task<ProductEventArgs>.Factory.StartNew(() => 
    { 
     try 
     { 
      // long operation which return new ProductEventArgs with a list of product 

     } 
     catch (Exception e) 
     { 
      return new ProductEventArgs() { E = e }; 
     } 

    }).ContinueWith((x) => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext()); 

Problem polega na tym, że nie mam limitu czasu. Chcę ustawić zegar, aby zwrócić coś takiego:

new ProductEventArgs() { E = new Exception("timeout") }; 

, jeśli przekroczono limit czasu. Nie można użyć Czekaj/Asynchronizuj. Wielkie dzięki!

Odpowiedz

4

Ten kod robi to, co wyraziło tutaj:

var timeout = TimeSpan.FromSeconds(5); 

var actualTask = new Task<ProductEventArgs>(() => 
{ 
    var longRunningTask = new Task<ProductEventArgs>(() => 
    { 
     try 
     { 
      Thread.Sleep(TimeSpan.FromSeconds(10)); // simulates the long running computation 
      return new ProductEventArgs(); 
     } 
     catch (Exception e) 
     { 
      return new ProductEventArgs() { E = e }; 
     } 
    }, TaskCreationOptions.LongRunning); 

    longRunningTask.Start(); 

    if (longRunningTask.Wait(timeout)) return longRunningTask.Result; 

    return new ProductEventArgs() { E = new Exception("timed out") }; 
}); 

actualTask.Start(); 

actualTask.Wait(); 

Console.WriteLine("{0}", actualTask.Result.E); // handling E 

Jak widać longRunningTask jest tworzony z TaskCreationOptions.LongRunning opcji. W ten sposób będzie on miał dedykowane Thread do jego wykonania i nie będzie kolidował z normalnym zachowaniem ThreadPool przez zajmowanie wątku stamtąd zbyt długo - co będzie potrzebne dla innych rzeczy, takich jak np. UI. To ważne dla długotrwałych zadań.

Uwaga: Można wtedy obsługiwać actualTask z ContinueWith, ale chciałem wyrazić istotę tutaj.

+1

Kocham Cię: - * Jestem nieco nowy na C# i bardziej na zarządzaniu wątek i zrobić mój dzień! – Rototo

1

Można uruchomić Task.Delay(timeout) zadania równolegle i sprawdzić, co było pierwsze zadanie do wykonania (Task.WhenAny() jest bardzo przydatny w tym przypadku):

public void FetchProduct(TimeSpan timeout) 
{ 
    var fetchTask = Task<ProductEventArgs>.Factory.StartNew(
     () => 
     { 
      try 
      { 
       // long operation which return new ProductEventArgs with a list of product 
      } 
      catch(Exception e) 
      { 
       return new ProductEventArgs() { E = e }; 
      } 
     }); 
    Task<ProductEventArgs> resultTask; 
    if(timeout != Timeout.InfiniteTimeSpan) 
    { 
     var timeoutTask = Task.Delay(timeout); 
     resultTask = Task.WhenAny(resultTask, timeoutTask).ContinueWith<ProductEventArgs>(
      t => 
      { 
       // completed task is the result of WhenAny 
       if(t.Result == fetchTask) 
       { 
        return fetchTask.Result; 
       } 
       else 
       { 
        return new ProductEventArgs() { E = new TimeoutException() }; 
       } 
      }); 
    } 
    else 
    { 
     resultTask = fetchTask; 
    } 
    resultTask.ContinueWith(x => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext()); 
} 

Należy zauważyć, że rozwiązanie to nie ma żadnej logiki rezygnacji, a twoje długo działające zadanie będzie nadal działać, nawet jeśli przekroczy limit czasu.

12

Należy użyć CancellationToken s:

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); 
var token = cts.Token; 
Task<ProductEventArgs>.Factory.StartNew(() => 
{ 
    try 
    { 
     // occasionally, execute this line: 
     token.ThrowIfCancellationRequested(); 
    } 
    catch (OperationCanceledException) 
    { 
     return new ProductEventArgs() { E = new Exception("timeout") }; 
    } 
    catch (Exception e) 
    { 
     return new ProductEventArgs() { E = e }; 
    } 

}).ContinueWith((x) => handleResult(x.Result), TaskScheduler.FromCurrentSynchronizationContext()); 
+0

Całkowicie się z tym zgadzam. Mam nadzieję, że OP może to zrobić - odpowiedziałem na podstawie (być może pochopnego) założenia, że ​​OP wywoływał jakiś kod poza kontrolą, która nie obsługiwała limitów czasu lub anulowania, więc założyłem, że nie może tego zrobić. Mam nadzieję, że może. :) –

+1

w twoim kodzie, po prostu muszę podać token jako drugi argument do metody startnew, prawda? Ponieważ tego nie napisałeś. Jeśli zrobiłem to i mam na przykład czas 0 sekund, mam wyjątek aggregateException na wynik obsługi (x.Result). Dzięki – Rototo

+0

Nie powinieneś przekazywać 'Case CancellationToken' do' StartNew' w twoim przypadku. Jeśli to zrobisz (jak zauważyłeś), już anulowany "CancellationToken" faktycznie anuluje zadanie przed jego uruchomieniem. Ale nie chcesz, aby zadanie zostało anulowane, więc nie jest to zachowanie, które chcesz. –

3

Można użyć zwrócony obiekt zadania dla metody StartNew a następnie metodę Wait użytkownikowi określić limit czasu.

Task<ProductEventArgs> task = Task<ProductEventArgs>.Factory.StartNew(() => {...}); 
if (!Task.Wait(new TimeSpan(0,0,1,0)) // wait for 1 minute 
{ 
    // throw exception or something else if timeout 
} 
0

Wystarczy uruchomić kolejne zadanie w ramach głównego zadania (zastępczy):

Task.Factory.StartNew(() => 
     { 
      // returns a string result 
      var tsk = new Task<string>(() => { return VeryImportantThingsToDo(); }); 
      try 
      { 
       tsk.Start(); 
       if (!tsk.Wait(5000)) 
        throw new TimeoutException(); 
       return tsk.Result; 
      } 
      catch (TimeoutException) 
      { 
       // Jabba Dabba Doooooooohhhhhh 
      } 

      return "<unknown>"; 
     }).ContinueWith((o) => string result = o.Result));