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
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. –
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. –
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. –