Как перехватывать исключения из ThreadPool.QueueUserWorkItem?
-
09-09-2019 - |
Вопрос
У меня есть следующий код, который выдает исключение:
ThreadPool.QueueUserWorkItem(state => action());
Когда действие генерирует исключение, моя программа выходит из строя.Какова наилучшая практика для решения этой ситуации?
Похожие: Исключения в потоках .Net ThreadPool
Решение
Если у вас есть доступ к action
исходный код, вставьте блок try/catch в этот метод;в противном случае создайте новый tryAction
метод, который оборачивает вызов action
в блоке try/catch.
Другие советы
Вы можете добавить try/catch следующим образом:
ThreadPool.QueueUserWorkItem(state =>
{
try
{
action();
}
catch (Exception ex)
{
OnException(ex);
}
});
Если вы используете .Net 4.0, возможно, стоит изучить Задача class, потому что он может позаботиться об этом за вас.
Эквивалент вашего исходного кода, но с использованием задач, выглядит так:
Task.Factory.StartNew(state => action(), state);
Чтобы справиться с исключениями, вы можете добавить продолжение к задаче, возвращаемой StartNew.Это может выглядеть так:
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);
В другом потоке (в методе, который вы «ставите в очередь», добавьте предложение try catch...Затем в catch поместите перехваченное исключение в общую переменную Exception (видимую для основного потока).
Затем в вашем основном потоке, когда все элементы в очереди завершены (используйте для этого массив дескрипторов ожидания), проверьте, не заполнил ли какой-либо поток это общее исключение исключением...Если это так, выбросьте его повторно или обработайте его соответствующим образом...
вот пример кода из недавнего проекта, для которого я это использовал...
HasException является общим логическим значением...
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;
}
То , что я обычно делаю , - это создаю большую попытку ...блок catch внутри метода action() затем сохраните исключение как закрытую переменную, а затем обработайте его внутри основного потока
Простой код:
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);
}
}