Исключения | Exception 
async Task ActionAsyncЕсли внутри метода возникнет исключение, то будет возвращен Task с исключением
Task ActionAsync

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

Если мы хотим сохранить общее поведение, то необходимо использовать конструкцию:
try
{
   return Task.CompletedTask;
}
catch (Exception ex)
{
   return Task.FromException(ex);
}

AsyncLocal
async Task ActionAsyncВ случае, если внутри метода будет задано значение AsyncLocal контейнера, то при выходе из метода оно будет сброшено на родительское значение.
Task ActionAsyncВ случае, если внутри метода будет задано значение AsyncLocal контейнера, то при выходе из метода оно не будет сброшено.
Выполнение кода
1)var tasks = Enumerable
    .Range(0, 10)
    .Select(
       async e => {
            action1();
           await Task.Delay(TimeSpan.FromSeconds(10));
            action2();
        }
    )
    .ToArray();
await Task.WhenAll(tasks);
action1 будут выполнены одним потоком (который вызвал Enumarable) последовательно друг за другом (без какого-либо параллелизма). После этого происходит асинхронное ожидание и прерывание потока исполнения.
action2 будет выполнено после ожидания при этом поток его выполнения зависит от параметров SynchronizationContext | Контекст синхронизации окружения (при этом могут исполняться параллельно друг другу в разных потоках.

Такой подход подходит для ситуации, когда мы не стремимся параллелить action1 (он не является ресурсоемким).
2)var tasks = Enumerable
    .Range(0, 10)
    .Select(
        e => Task.Run(
           async () => {
                action1();
               await Task.Delay(TimeSpan.FromSeconds(10));
                action2();
            }
            )
    )
    .ToArray();
await Task.WhenAll(tasks);
При таком вызове  на каждый отдельный элемент будет запущена отдельная задача через планировщик. Действия будут выполняться параллельно друг другу (в том числе и action1) в разных потоках. Будет запланировано 10 задач.
По умолчанию код, внутри делегата Task.Run (или Parallel.ForEachAsync) будет выполнен без контекста синхронизации, таким образом action2 будет запланирован к исполнению на потоке их пула.

Такой подход не очень хорош, если каждая операция не является ресурсоемкой с точки зрения процессора.
3) await Parallel.ForEachAsync(
    Enumerable.Range(0, 10),
   async (e, t) =>
    {
        action1();
       await Task.Delay(TimeSpan.FromSeconds(10));
        action2();
    }
    );
Данный метод позволяет выполнять действия параллельно, но при этом будет более экономичным чем 2 вариант т.к.

 

 

Теги: