Подтвердить что ты не робот

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

}
4b9b3361

Ответ 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();