Cómo capturar las excepciones de un ThreadPool.QueueUserWorkItem?
-
09-09-2019 - |
Pregunta
Tengo el siguiente código que se produce una excepción:
ThreadPool.QueueUserWorkItem(state => action());
Cuando la acción se produce una excepción, mi programa se bloquea. ¿Cuál es la mejor práctica para manejar esta situación?
Relacionado: en .Net ThreadPool Hilos
Solución
Si usted tiene acceso al código fuente del action
, insertar un bloque try / catch en ese método; de lo contrario, crear un nuevo método tryAction
que envuelve la llamada a action
en un bloque try / catch.
Otros consejos
Puede añadir try / catch como esta:
ThreadPool.QueueUserWorkItem(state =>
{
try
{
action();
}
catch (Exception ex)
{
OnException(ex);
}
});
Si está utilizando .Net 4.0, podría valer la pena investigar la clase Task , ya que puede hacerse cargo de esto para usted.
El equivalente de su código original, pero utilizando Tareas, parece
Task.Factory.StartNew(state => action(), state);
Para hacer frente a excepciones puede agregar una continuación de la tarea devuelta por StartNew. Se podría tener este aspecto:
var task = Task.Factory.StartNew(state => action(), state);
task.ContinueWith(t =>
{
var exception = t.Exception.InnerException;
// handle the exception here
// (note that we access InnerException, because tasks always wrap
// exceptions in an AggregateException)
},
TaskContinuationOptions.OnlyOnFaulted);
En el otro hilo, (en el método que está "haciendo cola", añada una cláusula intento de captura ... .A continuación, en la captura, coloque la excepción capturada en una variable compartida Excepción (visible para el hilo principal).
A continuación, en el hilo principal, cuando todos los artículos en cola han terminado (utilizar una matriz mango de espera para esto) Compruebe si algún hilo poblada esa excepción compartido con una excepción ... Si así fuera, volver a generar o manejarlo según corresponda. ..
aquí algunos ejemplos de código de un proyecto reciente he utilizado esto para ...
HasException es compartida booleano ...
private void CompleteAndQueuePayLoads(
IEnumerable<UsagePayload> payLoads, string processId)
{
List<WaitHandle> waitHndls = new List<WaitHandle>();
int defaultMaxwrkrThreads, defaultmaxIOThreads;
ThreadPool.GetMaxThreads(out defaultMaxwrkrThreads,
out defaultmaxIOThreads);
ThreadPool.SetMaxThreads(
MDMImportConfig.MAXCONCURRENTIEEUSAGEREQUESTS,
defaultmaxIOThreads);
int qryNo = 0;
foreach (UsagePayload uPL in payLoads)
{
ManualResetEvent txEvnt = new ManualResetEvent(false);
UsagePayload uPL1 = uPL;
int qryNo1 = ++qryNo;
ThreadPool.QueueUserWorkItem(
delegate
{
try
{
Thread.CurrentThread.Name = processId +
"." + qryNo1;
if (!HasException && !uPL1.IsComplete)
IEEDAL.GetPayloadReadings(uPL1,
processId, qryNo1);
if (!HasException)
UsageCache.PersistPayload(uPL1);
if (!HasException)
SavePayLoadToProcessQueueFolder(
uPL1, processId, qryNo1);
}
catch (MeterUsageImportException iX)
{
log.Write(log.Level.Error,
"Delegate failed " iX.Message, iX);
lock (locker)
{
HasException = true;
X = iX;
foreach (ManualResetEvent
txEvt in waitHndls)
txEvt.Set();
}
}
finally { lock(locker) txEvnt.Set(); }
});
waitHndls.Add(txEvnt);
}
util.WaitAll(waitHndls.ToArray());
ThreadPool.SetMaxThreads(defaultMaxwrkrThreads,
defaultmaxIOThreads);
lock (locker) if (X != null) throw X;
}
Lo que suelo hacer es crear un gran bloque try ... catch dentro del método action () a continuación, almacenar la excepción como una variable privada y luego manejarlo dentro del hilo principal
Código simple:
public class Test
{
private AutoResetEvent _eventWaitThread = new AutoResetEvent(false);
private void Job()
{
Action act = () =>
{
try
{
// do work...
}
finally
{
_eventWaitThread.Set();
}
};
ThreadPool.QueueUserWorkItem(x => act());
_eventWaitThread.WaitOne(10 * 1000 * 60);
}
}