Как дела, мальчики и девочки! Как некоторые из вас уже знают, я большой энтузиаст асинхронизма, я считаю его поистине прекрасной концепцией и всегда стараюсь максимально использовать его в своем коде и даже в других моих повседневных задачах, таких как ревью кода.

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

В этой статье основное внимание будет уделено одному специальному методу - Task.WhenAll - и показано, что он делает, как использовать его для повышения производительности нашего кода и обрабатывать исключения при его использовании.

В конце концов, моя цель состоит в том, чтобы, если вы согласны с моими идеями и реорганизуете свой код в соответствии с ними, вы почувствуете себя шеф-поваром, заканчивая идеальную еду, в конце концов, программирование и приготовление пищи не так уж и отличаются, если вы посмотрите на это очень внимательно. абстрактный способ!

public static Task WhenAll (params Task[] tasks);

Task.WhenAll создает задачу, которая будет завершена, когда будут выполнены все поставленные задачи. Этот метод делает довольно просто: он просто получает список Задач и возвращает Задачу, когда все полученные Задачи завершаются.

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

Вместо ожидания завершения каждой задачи перед запуском следующей мы можем сгруппировать эту рабочую нагрузку и выполнить ее с помощью метода Task.WhenAll. При этом все Задачи будут выполняться параллельно, и это сократит время выполнения до времени, необходимого для выполнения самой тяжелой Задачи.

Давайте начнем с того, что посмотрим на не очень удачную реализацию и посмотрим, сколько времени потребуется на ее выполнение:

I'm Jane and my work took 1006 ms
I'm John and my work took 2002 ms
Total Work took 3323 ms

Вскоре эта небольшая программа выполняет работу Джейн, выполнение которой занимает 1 секунду, а затем выполняет 2-секундную работу Джона, в итоге выполнение программы занимает около 3 секунд.

Но действительно ли нам нужно ждать завершения задания Джейн, прежде чем начинать задание Джона?

Быстрый ответ, нет! Поскольку результат первой Task не нужен для второй, мы можем выполнить их одновременно с помощью Task.WhenAll и выполнить обе задачи асинхронно, давайте посмотрим на рефакторинг первого кода с помощью Task.WhenAll:

I'm Jane and my work took 1004 ms
I'm John and my work took 2006 ms
Total Work took 2318 ms

Итак, мы видим, что обе Задачи были выполнены асинхронно, Task.WhenAll запускает обе Задачи одновременно и выполняет их. параллельно, результат таков, что вместо 3 секунд для запуска программы требуется всего 2 секунды, это огромное повышение производительности!

В этот момент вы, ребята, можете подумать:

«Хорошо, это здорово, и я вижу, насколько лучше использовать Task.WhenAll, но это действительно простые примеры. Что произойдет, если во время выполнения задачи возникнет исключение? »

Это было бы действительно отличное наблюдение! При использовании Task.WhenAll при обработке исключений возникают некоторые распространенные ошибки, поэтому давайте еще раз посмотрим на не очень хорошую реализацию в программе, которая генерирует исключения.

I'm Jane and my work took 1166 ms
I'm John and my work took 2004 ms
I'm Exception #1!
Total Work took 2288 ms

Глядя на вывод, мы замечаем, что с ним что-то не так, у нас должно быть два сообщения об исключении, а у нас только одно. Почему?

Обычно, когда мы используем Task.WhenAll, поскольку выполняется несколько задач , мы можем создать несколько исключений, и они будут заключены в AggregateException. AggregateException затем имеет свойство InnerExceptions, содержащее все исключения, возникшие во время выполнения.

Ключевое слово await всегда будет разворачивать первое исключение, возникшее в неисправной Задаче, поэтому в выходных данных у нас есть только одно из исключений. Поэтому, чтобы ничего не потерять, мы должны принять это во внимание и обрабатывать исключения немного по-другому.

I'm Jane and my work took 1153 ms
I'm John and my work took 2004 ms
I'm Exception #1!
I'm Exception #2!
Total Work took 2366 ms

В этом последнем примере мы не потеряли никакой информации, оба исключения были успешно пойманы и зарегистрированы, потому что мы просмотрели возвращенную задачу и перебрали ее InnerExceptions в AggregateException. Эта реализация работает вокруг await разворачивания первого исключения, из-за которого мы потеряли важную информацию, связанную с выполнением программы в предыдущем примере.

На данный момент я надеюсь, что вы, ребята, имеете четкое представление о том, как и когда использовать Task.WhenAll и как это повлияет на производительность вашего кода, а также некоторые распространенные ошибки, с которыми мы можем столкнуться при обработке исключений. и как их преодолеть.

Мой вам совет, ребята, - возьмите фрагменты кода, представленные в этой статье, и запачкайте руки. Нет лучшего способа научиться, чем запускать и отлаживать эти маленькие программы, чтобы увидеть, что на самом деле происходит.

Нельсон вон!