У меня есть 3 задачи:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
Все они должны быть запущены, прежде чем мой код сможет продолжить работу, и мне нужны результаты каждого из них. Ни один из результатов не имеет ничего общего друг с другом
Как мне позвонить и дождаться завершения 3 заданий, а затем получить результаты?
Ответы:
После использования
WhenAll
вы можете получить результаты по отдельности с помощьюawait
:Вы также можете использовать
Task.Result
(так как вы знаете, что к этому моменту они все успешно завершены). Тем не менее, я рекомендую использовать,await
потому что это явно правильно, хотяResult
может вызвать проблемы в других сценариях.источник
WhenAll
из этого полностью; ожидающие позаботятся о том, чтобы вы не прошли 3 более поздних задания, пока все задачи не будут выполнены.Task.WhenAll()
позволяет запустить задачу в параллельном режиме. Я не могу понять, почему @Servy предложил удалить его. БезWhenAll
них они будут бегать один за другимcatTask
он уже запущен к тому времени, как вернулсяFeedCat
. Таким образом, любой подход будет работать - единственный вопрос, хотите ли вы, чтобыawait
они по одному или все вместе. Обработка ошибок немного отличается - если вы используетеTask.WhenAll
, то они будутawait
все, даже если один из них рано выходит из строя.WhenAll
не влияет на то, когда выполняются операции или как они выполняются. У него есть только возможность повлиять на то, как наблюдаются результаты. В этом конкретном случае единственное отличие состоит в том, что ошибка в одном из первых двух методов привела бы к тому, что исключение было выдано в этот стек вызовов раньше в моем методе, чем у Стивена (хотя одна и та же ошибка всегда будет выдаваться, если есть какие-либо ).Просто
await
три задания по отдельности, после запуска их всех.источник
Task.WhenAll
буквально ничего не меняет в поведении программы любым видимым способом. Это чисто избыточный вызов метода. Вы можете добавить его, если хотите, в качестве эстетического выбора, но это не меняет действия кода. Время выполнения кода будет одинаковым как с вызовом этого метода, так и без него (ну, технически это приведет к очень небольшим издержкам при вызовеWhenAll
, но это должно быть незначительным), только делая эту версию немного более длинной для запуска, чем эта версия.WhenAll
- это чисто эстетическое изменение. Единственное заметное различие в поведении заключается в том, ожидаете ли вы, когда более поздние задачи завершатся в случае сбоя более ранней задачи, что обычно не требуется. Если вы не верите многочисленным объяснениям того, почему ваше утверждение неверно, вы можете просто запустить код для себя и убедиться, что оно неверно.Если вы используете C # 7, вы можете использовать удобный метод-обертку, как этот ...
... чтобы включить удобный синтаксис, подобный этому, когда вы хотите подождать несколько задач с разными типами возврата. Конечно, вам придется сделать несколько перегрузок для разного количества задач.
Тем не менее, посмотрите ответ Марка Гравелла о некоторых оптимизациях вокруг ValueTask и уже выполненных задач, если вы собираетесь превратить этот пример в нечто реальное.
источник
Task.WhenAll()
не возвращает кортеж. Один создается изResult
свойств предоставленных задач после того, как задача, возвращеннаяTask.WhenAll()
завершением..Result
звонки согласно рассуждениям Стивена, чтобы другие люди не увековечивали плохую практику, копируя ваш пример.Даны три задачи -
FeedCat()
,SellHouse()
иBuyCar()
, есть два интересных случая: либо они все полные синхронно (по какой - то причине, возможно кэширование или ошибка), или они не делают.Допустим, у нас есть, из вопроса:
Теперь простой подход будет следующим:
но ... это не удобно для обработки результатов; мы обычно хотели бы к
await
этому:но это приводит к большим накладным расходам и выделяет различные массивы (включая
params Task[]
массив) и списки (внутри). Это работает, но это не великое ИМО. Во многих отношениях проще использоватьasync
операцию и толькоawait
каждую по очереди:Вопреки некоторым комментариям выше, использование
await
вместо того, чтобы неTask.WhenAll
иметь никакого значения к тому, как выполняются задачи (одновременно, последовательно и т. Д.). На самом высоком уровнеTask.WhenAll
предшествует хорошей поддержке компилятора дляasync
/await
, и была полезна, когда таких вещей не было . Это также полезно, когда у вас есть произвольный массив задач, а не 3 дискретных задачи.Но: у нас все еще есть проблема, которая
async
/await
генерирует много шума компилятора для продолжения. Если есть вероятность , что задачи , возможно , на самом деле выполнить синхронно, то мы можем оптимизировать это путем создания в синхронном пути с асинхронным запасным вариантом:Такой подход «синхронизация с асинхронным резервом» становится все более распространенным, особенно в высокопроизводительном коде, где синхронные завершения встречаются относительно часто. Обратите внимание, что это совсем не поможет, если завершение всегда действительно асинхронное.
Дополнительные вещи, которые применяются здесь:
в недавнем C # общий шаблон для
async
резервного метода обычно реализуется как локальная функция:предпочитает ,
ValueTask<T>
чтобы ,Task<T>
если есть хороший шанс, когда - либо полностью синхронно с множеством различных возвращаемых значений:если это возможно, предпочитают ,
IsCompletedSuccessfully
чтобыStatus == TaskStatus.RanToCompletion
; теперь это существует в .NET Core дляTask
и везде дляValueTask<T>
источник
Task
когда все они сделаны без использования результатов.await
получить «лучшую» семантику исключений, исходя из предположения, что исключения являются редкими, но значимымиВы можете хранить их в задачах, а затем ждать их всех:
источник
var catTask = FeedCat()
выполняет функциюFeedCat()
и не сохраняет результат вcatTask
том, чтоawait Task.WhenAll()
часть становится бесполезной, поскольку метод уже выполнен?Если вы пытаетесь зарегистрировать все ошибки, убедитесь, что вы сохранили строку Task.WhenAll в своем коде, многие комментарии предполагают, что вы можете удалить ее и дождаться выполнения отдельных задач. Task.WhenAll действительно важно для обработки ошибок. Без этой строки вы потенциально оставляете свой код открытым для ненаблюдаемых исключений.
Представьте, что FeedCat выдает исключение в следующем коде:
В этом случае вы никогда не будете ждать ни houseTask, ни carTask. Здесь есть 3 возможных сценария:
SellHouse уже успешно завершен, когда FeedCat не удалось. В этом случае вы в порядке.
SellHouse не завершен и завершается с ошибкой в какой-то момент. Исключение не наблюдается и будет переброшено в поток финализатора.
SellHouse не является полным и содержит внутри него. В случае, если ваш код работает в ASP.NET, SellHouse потерпит неудачу, как только некоторые из них будут завершены. Это происходит из-за того, что вы в основном включили вызов и забыли, что контекст синхронизации был потерян, как только вышел из строя FeedCat.
Вот ошибка, которую вы получите для case (3):
В случае (2) вы получите похожую ошибку, но с оригинальной трассировкой стека исключений.
Для .NET 4.0 и более поздних версий вы можете ловить ненаблюдаемые исключения, используя TaskScheduler.UnobservedTaskException. Для .NET 4.5 и более поздних версий ненаблюдаемые исключения по умолчанию проглатываются, а для .NET 4.0 ненаблюдаемое исключение приведет к сбою процесса.
Подробнее здесь: Обработка исключений задач в .NET 4.5
источник
Вы можете использовать,
Task.WhenAll
как упоминалось, илиTask.WaitAll
, в зависимости от того, хотите ли вы, чтобы поток ожидал. Взгляните на ссылку для объяснения обоих.WaitAll vs WhenAll
источник
Используйте,
Task.WhenAll
а затем дождитесь результатов:источник
Предупреждение вперед
Просто быстрый заголовок для тех, кто посещает этот и другие подобные потоки, которые ищут способ распараллелить EntityFramework, используя набор инструментов async + await + task : показанный здесь шаблон является правильным, однако, когда речь идет о специальной снежинке EF, вы не будете достигайте параллельного выполнения до тех пор, пока вы не используете отдельный (новый) экземпляр db-context внутри каждого и каждого задействованного вызова * Async ().
Такого рода вещи необходимы из-за внутренних конструктивных ограничений ef-db-context, которые запрещают выполнять несколько запросов параллельно в одном экземпляре ef-db-context.
С учетом уже предоставленных ответов это способ убедиться, что вы собираете все значения даже в том случае, если одна или несколько задач приводят к исключению:
Альтернативная реализация, которая имеет более или менее одинаковые характеристики производительности, может быть:
источник
если вы хотите получить доступ к Cat, вы делаете это:
Это очень просто сделать и очень полезно использовать, нет необходимости искать сложное решение.
источник
dynamic
дьявол. Это для сложного взаимодействия COM и тому подобное, и не должно использоваться в любой ситуации, где это не является абсолютно необходимым. Особенно, если вы заботитесь о производительности. Или типа безопасность. Или рефакторинг. Или отладка.