Cómo atrapar excepciones de un ThreadPool.QueueUserWorkItem?

Tengo el siguiente código que arroja una excepción:

ThreadPool.QueueUserWorkItem(state => action()); 

Cuando la acción arroja una excepción, mi progtwig falla. ¿Cuál es la mejor práctica para manejar esta situación?


Relacionado: Excepciones en .Net ThreadPool Threads

Si tiene acceso al código fuente de la action , inserte un bloque try / catch en ese método; de lo contrario, cree un nuevo método tryAction que envuelva la llamada a la action en un bloque try / catch.

Puede agregar try / catch como este:

  ThreadPool.QueueUserWorkItem(state => { try { action(); } catch (Exception ex) { OnException(ex); } }); 

Si usa .Net 4.0, podría valer la pena investigar la clase Tarea porque puede encargarse de esto.

El equivalente a su código original, pero usando Tareas, se parece a

 Task.Factory.StartNew(state => action(), state); 

Para lidiar con las excepciones, puede agregar una continuación a la tarea devuelta por StartNew. Puede verse así:

 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”, agregue una cláusula try catch …. Luego, en el catch, coloque la excepción atrapada en una variable de excepción compartida (visible para el hilo principal).

Luego, en su hilo principal, cuando todos los artículos en cola hayan terminado (use una matriz de manejo de espera para esto) Compruebe si algún hilo llenó esa excepción compartida con una excepción … Si lo hizo, vuelva a lanzarlo o manipúlelo según corresponda …

aquí hay un código de muestra de un proyecto reciente que utilicé para …
HasException es booleano compartido …

  private void CompleteAndQueuePayLoads( IEnumerable payLoads, string processId) { List waitHndls = new List(); 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 bash … catch block dentro del método action () luego almacenar la excepción como una variable privada y luego manejarla dentro del hilo principal