2013-05-17 8 views
9

Devo eseguire un'operazione lungo in un thread e continuare restituendo il risultato a una funzione. Qui è il mio codice:Timeout factory task C#

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

Il problema è in realtà non ho un timeout. Voglio mettere un timer per restituire qualcosa del genere:

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

se il timeout è raggiunto. Non posso usare attendere/asincrare. Grazie mille!

risposta

4

Questo codice fa quello che avete espresso qui:

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 

Come vedete longRunningTask viene creato con TaskCreationOptions.LongRunning opzione. In questo modo avrà uno Thread dedicato per la sua esecuzione e non interferirà con il normale comportamento di ThreadPool occupando troppo a lungo un thread - che sarà necessario per altre cose come l'interfaccia utente. Questo è importante per le attività di lunga durata.

Nota: è possibile gestire actualTask con ContinueWith ma volevo esprimere l'essenza qui.

+1

Amore voi: - * Sono un po 'nuova su C# e più sulla gestione dei thread e si fanno il mio giorno! – Rototo

1

È possibile eseguire un'attività Task.Delay(timeout) in parallelo e verificare quale compito è stato il primo a completare (Task.WhenAny() è molto utile in questo caso):

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

Si noti che questa soluzione non ha alcuna logica cancellazione, e la tua attività a esecuzione prolungata sarà ancora in esecuzione anche se scade.

12

si dovrebbe usare 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

Sono completamente d'accordo con questo. Speriamo che l'OP possa farlo - ho risposto basandomi sull'ipotesi (forse azzardata) che l'OP stesse chiamando un codice fuori dal suo controllo che non supportava timeout o cancellazioni, quindi ho pensato che non potesse farlo. Spero che possa. :) –

+1

nel tuo codice, devo solo dare il token come secondo argomento per il metodo startnew, giusto? Perché non l'hai scritto. Se l'ho fatto, e ho un timestan di 0sec per esempio, ho un aggregateException sul risultato handle (x.Result). Grazie – Rototo

+0

Non dovresti passare 'CancellationToken' a' StartNew' nel tuo caso. Se lo fai (come hai osservato), un 'CancellationToken' già cancellato cancellerà effettivamente l'attività prima che inizi. Ma non vuoi che l'attività venga completata, quindi non è il comportamento che desideri. –

3

È possibile utilizzare oggetti compito restituito per il metodo StartNew e poi il metodo utente Attendere per determinare il timeout.

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

Basta avviare un'altra attività all'interno del compito principale (surrogato):

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