Перехват исключений из сложных цепочек задач

#c# #exception #.net-core #async-await #task-parallel-library

#c# #исключение #.net-ядро #асинхронный-ожидание #задача-параллельная-библиотека

Вопрос:

У меня есть .Net core приложение, которое в основном делает что-то вроде этого:

 public async Task<IOBoundStuffResult> DoIOBoundStuff()
{
    // Do IO bound stuff ...
    
    return new IOBoundStuffResult
    {
        Id = getIdForThing()
    };
}

public async Task<OtherIOBoundStuffResult> DoOtherIOBoundStuff()
{
    // Do other IO bound stuff ...
    
    return new OtherIOBoundStuffResult
    {
        Uri = getUriForThing()
    };
}

public async Task<IOBoundTaskResult> DoIOBoundStuff()
{
    var ioBoundTask1 = doIOBoundStuff();
    var ioBoundTask2 = doOtherIOBoundStuff();
    
    return await Task.WhenAll(ioBoundTask1, ioBoundTask2)
        .ContinueWith((task) =>
        {
            var id = ioBoundTask1.Result.Id;
            var uri = ioBoundTask2.Result.Uri;
            
            doSomethingWithIdAndUri(id, uri);
            
            return new IOBoundTaskResult
            {
                Id = id,
                Uri = uri
            };
        });
}

public async Task<IActionResult> DoThing()
{
    try
    {
        var cpuBoundTask = Task.Run(() =>
        {
            doCPUBoundStuff();
        });
        
        var ioBoundTask = DoIOBoundStuff();
        
        // do stuff with ioBoundTask, cpuBoundTask
    }
    catch (System.Exception ex)
    {
        // Process System.Exception.AggregateException, other exceptions
    }
}
 

Проблема здесь в том, что если что-то в одной из этих задач выдает исключение (в частности doSomethingWithIdAndUri() ), то это исключение не перехватывается блоком try …catch и приводит к сбою. Я пробовал создавать задачи продолжения с TaskContinuationOptions.OnlyOnFaulted помощью обработки исключений, но все, что, похоже, делает, это всегда вызывает TaskCancelledException выброс a . Как я могу перехватывать исключения, которые генерируются из задач?

Комментарии:

1. Ожидать их? [Добавление символов в соответствии с требованиями]

2. @PeterBons Да, я ожидаю cpuBoundTask, ioBoundTask, если вы это имеете в виду.

3. Я вижу задачу. Запуск, который, похоже, не ожидается, это правильно?

Ответ №1:

Для того, чтобы исключение задачи «всплывало», его нужно ожидать. Если при ожидании было выдано исключение, это исключение будет повторно выдано в текущем контексте (хотя иногда оно может быть обернуто в другой тип исключения, например, an AggregateException .

Вам нужно либо изменить, например, var ioBoundTask = DoIOBoundStuff(); на var ioBoundTask = await DoIOBoundStuff(); , либо перехватить исключения в другом месте, ожидая выполнения задач и заключая ожидания в другой блок try-catch .

Позаботьтесь о том, чтобы ожидать каждого отдельного асинхронного метода. Избегайте использования async void , поскольку вы не можете их ожидать. Вместо этого используйте async Task .

ОТРЕДАКТИРОВАНО для добавления: ваше использование ContinueWith is кажется немного странным в асинхронном контексте. Вместо того , чтобы

 return await Task.WhenAll(ioBoundTask1, ioBoundTask2)
        .ContinueWith((task) =>
        {
            var id = ioBoundTask1.Result.Id;
            var uri = ioBoundTask2.Result.Uri;
            
            doSomethingWithIdAndUri(id, uri);
            
            return new IOBoundTaskResult
            {
                Id = id,
                Uri = uri
            };
        });
 

Вы могли бы написать что-то вроде

 var result1 = await ioBoundTask1;
var result2 = await ioBoundTask2;

var id = result1.Id;
var uri = result2.Uri;
            
doSomethingWithIdAndUri(id, uri);
            
return new IOBoundTaskResult
{
    Id = id,
    Uri = uri
};
 

Это делает более понятным, что происходит, и упрощает предотвращение таких проблем, как исключения, выбрасываемые в неправильном месте / вообще не выбрасываемые. Бывают ситуации, когда требуется более явное использование продолжений, но в целом этого следует избегать в пользу более чистого, более сжатого синтаксиса async / await.

РЕДАКТИРОВАТЬ: вот ссылка на мою версию кода, где исключение должно «всплывать» правильно.

Комментарии:

1. Да, проблема заключалась в том, что doSomethingWithIdAndUri был асинхронным void . Изменение этого и ожидание его исправили это. Спасибо.