Задание и исключение
Почему исключения, заданные в задаче, являются молчащим исключением, и вы никогда не знаете, было ли выбрано какое-то исключение.
try
{
Task task = new Task(
() => {
throw null;
}
);
task.Start();
}
catch
{
Console.WriteLine("Exception");
}
программа запускается в полной тишине!
где поведение потоков различно
try
{
Thread thread = new Thread(
() => {
throw null;
}
);
thread .Start();
}
catch
{
Console.WriteLine("Exception");
}
в этом случае будет выбрано исключение нулевого указателя.
В чем разница?
Ответы
Ответ 1
Поведение этого сценария зависит от того, какие рамки у вас есть; в 4.0, вам действительно нужно быть осторожным - если вы не обрабатываете TaskScheduler.UnobservedTaskException
, он будет позже, когда он будет собран/финализирован и будет убить ваш процесс.
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
Trace.WriteLine(args.Exception.Message); // somebody forgot to check!
args.SetObserved();
};
Это изменяется в версии 4.5, IIRC.
Чтобы проверить результат Task
, который может выйти из строя, вы можете зарегистрировать продолжение - т.е. вызвать ContinueWith
и проверить исключение результата. В качестве альтернативы, доступ к .Result
задачи (которая также будет делать неявный Wait()
), повторит возникшее исключение. Хорошо наблюдать результат задачи, поскольку это очищает флаг финализации, то есть его можно собирать более дешево.
Ответ 2
Нет, задачи не являются нитями. Задачи представляют собой абстракцию высокого уровня - они представляют собой единицу работы, которая по своей сути параллелизуема. Потоки запускают единицы работы.
В первом примере вы создаете единицу работы, а затем говорите ей, что она запускается сама (как это делается, это деталь реализации задачи). Если во втором примере вы четко планируете единицу работы (она будет выглядеть по-другому для реализации Задачи).
Ответ 3
Следующее предполагает .NET 4.0 и использование TaskScheduler по умолчанию.
Прежде всего обратите внимание на то, что исключения исключаются из делегатов, которые вы передаете, поэтому они создаются в другом потоке, а не в том, что вы (логически) выполняете свой catch
. Поэтому каким-то образом исключение должно распространяться из потока, который выполняет ваш делегат/лямбда-код, в тот, который запустил поток/задачу.
Обратите внимание, что для Task
я думаю, что библиотека может также не запускать ее в своем потоке, а скорее в вызывающем потоке (но я не уверен, что это верно только для Parallel.ForEach
, и т.д., а не для "голого" объекта Task
).
Для этого два случая должны выполняться две вещи:
- Вызывающий поток по-прежнему активен. В противном случае нет ничего, что могло бы реально выполнить catch.
- Исключение, которое было вызвано в потоке/задаче, должно каким-то образом быть сохранено и ререйзировано для вас, чтобы поймать его.
Сказав это, оба ваших примера не дождались завершения потока/задачи. В первом примере вам не хватает task.Wait()
(или аналогичного), а во втором a thread.Join()
. В зависимости от поведения синхронизации тестовых кодов это может означать, что вы никогда не сможете наблюдать исключение из потока/задачи (пункт 1 выше).
Даже если вы добавите два вызова, это происходит для меня (опять же .NET 4.0):
-
Task Пример: вызов task.Wait()
на самом деле ререйз исключение первоначально оставил необработанное в целевой делегат (это то, что TPL будет делать для вас внутри), это обернуть его внутри a System.AggregateException
, который вы могли бы/увидели бы, если бы вы использовали что-то более точное, чем плоское "catch-all".
-
Тема Пример: исключение поднятый делегатом остается необработанной, и ваши выходы приложений (если не делать ничего, чтобы иметь дело с необработанные исключения по-разному)
Другими словами, у меня были бы следующие примеры:
// Thread example
var thread = new Thread(() => { throw null; });
thread.Start();
thread.Join();
// Should never reach here, depending on timing sometimes not even
// until the Join() call. The process terminates as soon as the other
// thread runs the "throw null" code - which can logically happen somewhere
// after the "start" of the "Start()" call.
// Task example
try
{
var task = new Task(() => { throw null; });
task.Start();
task.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine("Exception: " + ex);
}
Ответ 4
Я думаю, что вы не получаете исключения в случае Task
, потому что вы не ожидаете исключения в основном потоке. Вы просто продолжаете. Поместите task.Wait()
, и вы получите исключение в основном потоке.