La forma de hacerlo es con un CancellationToken y el nuevo modelo de cancelación. El nuevo modelo de cancelación está integrado en .NET Framework en varios tipos. Los más importantes son System.Threading.Tasks, System .Threading.Tasks.Task, System.Threading.Tasks.Task y System.Linq.ParallelEnumerable.
Aquí hay un ejemplo de su problema. Este código siempre se estancará porque el código de llamada toma primero un bloqueo y luego la tarea interbloqueada intenta obtener el mismo bloqueo.
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;
}
}
}
La cancelación de tareas en el TPL es cooperativa. En otras palabras, esto siempre se estancará porque nada maneja el token de cancelación que se configura como cancelado porque el hilo de la tarea está bloqueado.
Hay una forma de evitar esto, pero todavía se basa en los autores del código no confiable para hacer lo correcto:
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;
}
}
Puntos a tener en cuenta; la cancelación es cooperativa. Puede usar Token.WaitHandle para obtener un identificador y esperarlo junto con los identificadores de otras primitivas de sincronización. Mutex es mucho más lento que Monitor (o bloqueo).
Realmente, si no confías en el autor del código lo suficiente como para que implementen la cancelación cooperativa, entonces cuestionaría la cordura de que se ejecuten dentro de tu AppDomain en el mismo hilo.
Para más detalles ver:
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
Gracias, esta es información útil. Sin embargo, tenía la impresión de que corresponde a la tarea escuchar la solicitud de cancelación y lanzar OperationCancelledException por sí solo si no se puede completar por completo. Voy a probar tu código cuando tenga oportunidad esta tarde. –
Desde el primer enlace, "los oyentes pueden recibir notificaciones de solicitudes de cancelación por sondeo, registro de devolución de llamada o espera en los identificadores de espera". Tan eficazmente Task.Wait hace que la escucha ocurra. –
Ver: http://stackoverflow.com/questions/2293976/how-and-if-to-write-a-single-consumer-queue-using-the-task-parallel-library/2779208#2779208 para ver un ejemplo de utilizando IsCancellationRequested para verificar la cancelación y responder a ella. –