Задание и исключение

Почему исключения, заданные в задаче, являются молчащим исключением, и вы никогда не знаете, было ли выбрано какое-то исключение.

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(), и вы получите исключение в основном потоке.