6

Vorrei iniziare a utilizzare lo Task Parallel Library, poiché questo è il framework consigliato in futuro per eseguire operazioni asincrone. Una cosa che non sono stato in grado di trovare è qualsiasi mezzo di Aborto forzato, come quello che fornisce Thread.Abort.Eliminazione di un'attività bloccata in .NET 4 TPL

La mia particolare preoccupazione è che pianifico attività in esecuzione di codice di cui non desidero completamente fidarmi. In particolare, non posso essere sicuro che questo codice non attendibile non si blocchi e quindi non posso essere certo se una Task che pianifico usando questo codice non si completerà mai. Voglio stare lontano dal vero isolamento di AppDomain (a causa del sovraccarico e della complessità del marshalling), ma non voglio nemmeno lasciare un thread di Task in agguato, deadlock. C'è un modo per farlo in TPL?

risposta

9

Il modo per farlo è con un metodo di annullamento e il nuovo modello di annullamento Il nuovo modello di annullamento è integrato in .NET Framework in diversi tipi, i più importanti sono System.Threading.Tasks, System .Threading.Tasks.Task, System.Threading.Tasks.Task e System.Linq.ParallelEnumerable.

Ecco un esempio del tuo problema. Questo codice sarà sempre deadlock perché il codice chiamante prende prima un blocco e quindi l'attività deadlock tenta di acquisire lo stesso blocco.

public void Example() 
{ 
    object sync = new Object(); 
    lock (sync) 
    { 
     CancellationTokenSource canceller = new CancellationTokenSource(); 
    ManualResetEvent started = new ManualResetEvent(false); 
     Task deadlocked = Task.Factory.StartNew(() => 
      { 
      started.Set(); 
       // EVIL CODE: This will ALWAYS deadlock 
       lock(sync) { }; 
      }, 
      canceller.Token); 

     // Make sure task has started. 
    started.WaitOne(); 

     canceller.Cancel(); 

     try 
     { 
      // Wait for task to cancel. 
      deadlocked.Wait(); 
     } 
     catch (AggregateException ex) 
     { 
      // Ignore canceled exception. SIMPLIFIED! 
      if (!(ex.InnerException is TaskCanceledException)) 
       throw; 
     } 
    } 
} 

L'annullamento dell'attività in TPL è cooperativo. In altre parole, questo sarà sempre deadlock perché nulla gestisce il token di annullamento impostato su annullato perché il thread dell'attività è bloccato.

C'è un modo per aggirare questo, ma si basa ancora sul del codice non attendibile agli autori di fare la cosa giusta:

public static void Example2() 
{ 
    Mutex sync = new Mutex(true); 

    CancellationTokenSource canceller = new CancellationTokenSource(); 
    bool started = false; 

    Task deadlocked = Task.Factory.StartNew(() => 
     { 
      started = true; 
      // EVIL CODE: This will ALWAYS deadlock 
      WaitHandle.WaitAny(new WaitHandle[] { canceller.Token.WaitHandle, sync }); 
     }, 
     canceller.Token); 

    // Make sure task has started. 
    while (!started) { } 

    canceller.Cancel(); 

    try 
    { 
     // Wait for task to cancel. 
     deadlocked.Wait(); 
    } 
    catch (AggregateException ex) 
    { 
     // Ignore canceled exception. SIMPLIFIED! 
     if (!(ex.InnerException is TaskCanceledException)) 
      throw; 
    } 
} 

Punti da notare; la cancellazione è cooperativa. È possibile utilizzare Token.WaitHandle per ottenere un handle e attendere su di esso insieme agli handle di altre primitive di sincronizzazione. Mutex è molto più lento di Monitor (o blocco).

Davvero se non ti fidi abbastanza dell'autore del codice per farli implementare la cancellazione cooperativa, allora metterei in dubbio la sanità di averli eseguiti all'interno del tuo AppDomain sullo stesso thread.

Per ulteriori dettagli si veda:

http://msdn.microsoft.com/en-us/library/dd997364.aspx

http://msdn.microsoft.com/en-us/library/dd537607.aspx

http://msdn.microsoft.com/en-us/library/ee191552.aspx

+0

Grazie, questa è un'informazione utile. Tuttavia, ho avuto l'impressione che spetti al compito ascoltare la richiesta di annullamento e lanciare OperationCancelledException da solo se non è stato completamente completato. Proverò il tuo codice quando avrò la possibilità questo pomeriggio. –

+0

Dal primo collegamento, "Gli ascoltatori possono essere informati delle richieste di annullamento mediante polling, registrazione di callback o attesa in attesa di handle". In modo efficace Task.Wait fa sì che l'ascolto si verifichi. –

+0

Vedere: http://stackoverflow.com/questions/2293976/how-and-if-to-write-a-single-consumer-queue-using-the-task-parallel-library/2779208#2779208 per un esempio di utilizzando IsCancellationRequested per verificare la cancellazione e rispondere ad esso. –

-4

È sufficiente chiamare Task.Wait(timespanToWait).

Se l'attività non è completa dopo il periodo specificato, viene annullata.

+0

Grazie. Questo non era affatto ovvio dalla documentazione, ma ha senso quando si pensa a Tasks come se si isolasse da qualsiasi dettaglio sul modo in cui sono pianificati e gestiti i Compiti. Sai se dovrò fare qualcosa di speciale dopo i ritorni di attesa prima di poter smaltire in modo sicuro l'attività? –

+11

Questa non è la risposta. Wait() farà proprio questo, aspetta. Non annullerà/annullerà l'attività. –

0

Dan Non penso che Task.Wait (timeout) annulli questa attività, c'è Overload Task.Wait (timeout, cancelationToken), ma che solleva OperationCanceledException sull'attività. Attendi quando viene segnalato il token.

Task.Attendere solo blocchi fino al termine di uno dei task o al timeout, non annullare o interrompere l'attività stessa. L'attività così bloccata rimarrà sospesa in ThreadPool. Non è possibile eliminare l'attività non completata (InvalidOperation).

Im scrivendo lo stesso tipo di applicazione come voi e ho scritto la mia TaskScheduler che permette Interruzione (e non sta usando threadpool :().

Ma im molto curioso di sapere come avete risolto questo problema. Si prega di rispondere

+0

Dopo un'ulteriore considerazione, ho deciso che se si verificava un deadlock, Thread.Abort si limita a mascherare qualsiasi problema sottostante, poiché lo stato potrebbe essere già stato danneggiato. Come tale, sto considerando di non riuscire a terminare per essere fatale (l'operatore ha l'opzione di continuare ad aspettare o terminare l'applicazione). Questo è sufficiente per la mia app in quanto non è mission critical; se l'app fosse mission-critical, dovrei migrare a utilizzare AppDomains o un processo di hosting separato per il codice parzialmente attendibile. –

+0

Dan, penso che questo sia il modo corretto di pensarci. Thread.Abort non è sicuramente una soluzione. Potrebbe lasciare la tua applicazione in uno stato sconosciuto. –