Различные обработки исключений между Task.Run и Task.Factory.StartNew
У меня возникла проблема, когда я использовал Task.Factory.StartNew
и попытался захватить exception
, который вызывается. В моем приложении у меня есть длительная работа, которую я хочу инкапсулировать в Task.Factory.StartNew(.., TaskCreationOptions.LongRunning);
Однако исключение не поймано, когда я использую Task.Factory.StartNew
. Это работает, как я ожидаю, когда я использую Task.Run
, который, как я думал, был всего лишь оберткой на Task.Factory.StartNew
(согласно, например, этой статье MSDN).
Здесь приведен рабочий пример, причем разница заключается в том, что исключение записывается в консоль при использовании Task.Run
, но не при использовании Factory.StartNew
.
Мой вопрос:
если у меня есть задача LongRunning
, которая имеет возможность генерировать исключения, как я должен обрабатывать их в вызывающем коде?
private static void Main(string[] args)
{
Task<bool> t = RunLongTask();
t.Wait();
Console.WriteLine(t.Result);
Console.ReadKey();
}
private async static Task<bool> RunLongTask()
{
try
{
await RunTaskAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
Console.WriteLine("success");
return true;
}
private static Task RunTaskAsync()
{
//return Task.Run(async () =>
// {
// throw new Exception("my exception");
// });
return Task.Factory.StartNew(
async () =>
{
throw new Exception("my exception");
});
}
Ответы
Ответ 1
Ваша проблема в том, что StartNew
не работает как Task.Run
с делегатами async
. Возвращаемый тип StartNew
равен Task<Task>
(который можно конвертировать в Task
). "Внешний" Task
представляет начало метода, а "внутренний" Task
представляет завершение метода (включая любые исключения).
Чтобы добраться до внутреннего Task
, вы можете использовать Unwrap
. Или вы можете просто использовать Task.Run
вместо StartNew
для кода async
. LongRunning
- это всего лишь подсказка по оптимизации и действительно необязательна. Стивен Тууб имеет хорошее сообщение в блоге о разнице между StartNew
и Run
и почему Run
(обычно) лучше для кода async
.
Обновление из комментария @usr ниже: LongRunning
применяется только к началу метода async
(до первой незавершенной операции await
ed). Поэтому в этом случае почти наверняка лучше использовать Task.Run
.
Ответ 2
Я отвечу на некоторые мои комментарии в ответ, потому что они оказались полезными:
LongRunning
идентичен принудительному созданию нового потока на практике. И ваш метод async, вероятно, долгое время не работает в этом потоке (он снимается с первой точки ожидания). В этом случае вы не хотите LongRunning.
Не имеет значения, как долго работает асинхронный метод. Нить уничтожается с самого первого ожидания (что работает по незавершенной задаче).
Может ли компилятор каким-либо образом использовать этот намек? Компилятор обычно не может анализировать ваш код каким-либо основным способом. Также компилятор ничего не знает о TPL. TPL - это библиотека. И эта библиотека всегда будет запускать новый поток. Задайте LongRunning
, если ваша задача почти всегда будет записывать 100% процессор в течение нескольких секунд или будет блокироваться в течение нескольких секунд с очень высокой вероятностью.
Мое предположение: вы не хотите LongRunning
здесь, потому что, если вы блокируете, почему вы используете асинхронный вызов в первую очередь? async не блокирует, а выходит из потока.
Ответ 3
Это должно быть возможно, если вы сначала Unwrap задание:
await RunTaskAsync().Unwrap();
Или, альтернативно:
await await RunTaskAsync();