Различная обработка исключений между 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");
    });

}

3 ответа

Решение

Ваша проблема в том, что 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 ред). Так что почти наверняка лучше использовать Task.Run в этом случае.

Я приведу некоторые из моих комментариев в ответ, потому что они оказались полезными:

LongRunning идентично принудительному созданию нового потока на практике. И ваш асинхронный метод, вероятно, не находится в этом потоке в течение длительного времени (он отключается в первой точке ожидания). Вы не хотите LongRunning в этом случае.

Неважно, как долго работает асинхронный метод. Поток уничтожается с самого первого ожидания (который работает с незавершенной задачей).

Может ли компилятор использовать эту подсказку каким-либо образом? Компилятор, как правило, не может анализировать ваш код каким-либо существенным образом. Также компилятор ничего не знает о TPL. TPL - это библиотека. И эта библиотека просто всегда запускает новый поток. Уточнить LongRunning если ваша задача почти всегда будет сжигать 100% ЦП в течение нескольких секунд или будет блокироваться в течение нескольких секунд с очень высокой вероятностью.

Я думаю, ты не хочешь LongRunning здесь, потому что, если вы блокируете, почему вы используете асинхронный в первую очередь? Async - это не блокировка, а выход из потока.

Это должно быть возможно при первом развертывании задачи:

await RunTaskAsync().Unwrap();

Или в качестве альтернативы:

await await RunTaskAsync();
Другие вопросы по тегам