Задача и исключение тишины - PullRequest
17 голосов
/ 10 января 2012

Почему исключения, генерируемые в задаче, являются тихим исключением, и вы никогда не узнаете, было ли выброшено определенное исключение

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");
 }

В этом случае будет сгенерировано исключение нулевого указателя. В чем разница?

Ответы [ 4 ]

15 голосов
/ 10 января 2012

Поведение этого сценария зависит от того, какая у вас структура;в 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()) переотобразит возникшее исключение.Хорошо наблюдать за результатом задачи, поскольку он очищает флаг завершения, что означает, что его можно собрать дешевле.

5 голосов
/ 10 января 2012

Нет, задачи не являются потоками. Задачи представляют собой абстракцию высокого уровня - они представляют собой единицу работы, которая по своей природе параллельна. Потоки запускают единицы работы.

В первом примере вы создаете единицу работы, а затем указываете ей, что она должна выполняться сама (как это происходит, - деталь реализации Task). Принимая во внимание, что во втором примере вы явно запланируете единицу работы (это будет отличаться от реализации Задачи).

4 голосов
/ 10 января 2012

Следующее предполагает .NET 4.0 и использование TaskScheduler по умолчанию.

Прежде всего, обратите внимание, что исключения вызываются внутри делегатов, которых вы проходите, поэтому они вызываются в другом потоке, а не в том, который вы (логически) выполняете в catch. Так или иначе, исключение должно быть передано из потока, который выполняет ваш делегатский / лямбда-код, в тот, который запустил поток / задачу.

Обратите внимание, что для Task я думаю, что библиотека также может выбрать не запускать ее в своем собственном потоке, а в вызывающем потоке (но я не уверен, что это верно только для Parallel.ForEach, и т. д., а не для "голого" Task объекта).

Для того чтобы это произошло, должны быть выполнены две вещи:

  1. Вызывающий поток все еще активен. Иначе не останется ничего, что могло бы фактически выполнить улов.
  2. Исключение, которое было вызвано в потоке / задаче, должно как-то сохраняться и повторно вызываться, чтобы вы могли его перехватить.

С учетом вышесказанного оба ваших примера не ждут завершения потока / задачи. В первом примере вам не хватает task.Wait() (или аналогичного), а во втором thread.Join(). В зависимости от поведения времени выполнения тестовых кодов это может означать, что вы никогда не сможете наблюдать исключение из потока / задачи (пункт 1 выше).

Даже если вы добавите два вызова, это то, что происходит со мной (снова .NET 4.0):

  • Пример задачи : вызов task.Wait() фактически вызывает реанимацию исключения, первоначально оставленного необработанным в делегате задачи (это то, что TPL сделает для вас внутри), он переносит его внутрь System.AggregateException, который вы могли бы / увидели, если бы использовали что-то более точное, чем плоское «универсальное».

  • Тема пример: исключение, выдвинутое делегатом, остается необработанным, и ваше приложение закрывается (если вы не сделаете что-либо для необработанных исключений иначе )

Другими словами, у меня были бы следующие примеры:

// 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);
}
1 голос
/ 10 января 2012

Я думаю, что вы не получаете исключение в случае Task, потому что вы не ожидаете исключения в основном потоке.Вы просто продолжаете.Поставьте task.Wait(), и вы получите исключение в основном потоке.

...