Я смотрю на асинхронное программирование на C #, и мне было интересно, какая разница между этими функциями, которые делают одно и то же и все ожидают.

public Task<Bar> GetBar(string fooId)
{
    return Task.Run(() =>
    {
        var fooService = new FooService();
        var bar = fooService.GetBar(fooId);
        return bar;
    });
}

public Task<Bar> GetBar(string fooId)
{
    var fooService = new FooService();
    var bar = fooService.GetBar(fooId);
    return Task.FromResult(bar)
}

public async Task<Bar> GetBar(string fooId)
{
    return await Task.Run(() =>
    {
        var fooService = new FooService();
        var bar = fooService.GetBar(fooId);
        return bar;
    });
}

Я предполагаю, что первый - это правильный способ сделать что-то, и код не выполняется, пока вы не попытаетесь получить результат из возвращенной задачи.

Во втором код выполняется по вызову, а результат сохраняется в возвращенной задаче.

А третий вроде как второй? Код выполняется по вызову, а результат Task.Run возвращается? Что в таком случае было бы глупо с этой функцией?

Я прав или я далеко?

6
Gralov 22 Фев 2016 в 13:05

2 ответа

Лучший ответ

Ни одна из этих реализаций метода не имеет смысла. Все, что вы делаете, это переносите блокирующую работу в пул потоков (или, что еще хуже, запускаете его синхронно и оборачиваете результат в экземпляр Task<Bar>). Вместо этого вам следует предоставить доступ к синхронному API и позволить вызывающим абонентам решать, как его вызывать. Тогда им решать, хотят они использовать Task.Run или нет.

Сказав это, вот различия:

< Сильный > # 1

Первый вариант (который напрямую возвращает Task<Bar>, созданный через Task.Run) является «самым чистым», даже если он не имеет особого смысла с точки зрения API. Вы позволяете Task.Run планировать данную работу в пуле потоков и возвращать Task<Bar> вызывающему завершению асинхронной операции.

# 2

Второй метод (который использует Task.FromResult) не асинхронный. Он выполняется синхронно, как обычный вызов метода. Результат просто помещается в завершенный экземпляр Task<Bar>.

< Сильный > # 3

Это более запутанная версия первой. Вы достигли результата, аналогичного тому, что делает №1, но с дополнительным, ненужным и даже несколько опасным await. На этот стоит остановиться подробнее.

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

Однако ничего из вышеперечисленного не принесет никакой пользы в вашем сценарии, потому что у вас только один Task . Следовательно, нет необходимости заставлять компилятор генерировать для вас конечный автомат только для выполнения того, что уже делает Task.Run.

Плохо спроектированный метод async также может быть опасен. Не используя ConfigureAwait(false) на своем await ed Task, вы непреднамеренно вводите SynchronizationContext захват, снижая производительность и создавая риск взаимоблокировки без всякой пользы.

Если ваш вызывающий абонент решает заблокировать ваш Task<Bar> в среде, которая имеет SynchronizationContext (например, Win Forms, WPF и возможно ASP.NET) через GetBar(fooId).Wait() или GetBar(fooId).Result, они попадут в тупик по причинам, описанным здесь.

6
Kirill Shlenskiy 22 Фев 2016 в 10:46

Я где-то читал на Stackoverflow в комментарии следующую аналогию. Поскольку это находится в комментарии, я не могу его легко найти, поэтому ссылки на него нет.

Предположим, вам нужно приготовить завтрак. Вы варите яйца и поджариваете хлеб.

Если вы начнете варить яйца, то где-то в подпрограмме «Варить яйцо» придется ждать, пока яйца закипят.

Синхронным было бы то, что вы ждете, пока яйца не закончатся, перед запуском подпрограммы «Toast Bread».

Однако будет более эффективно, если пока яйца варятся, вы не ждете, а начинаете жарить яйца. Затем вы ждете, пока один из них закончит, и продолжаете процесс «Сварить яйца» или «Тост для тостов», в зависимости от того, что закончится раньше. Это асинхронно, но не одновременно. По-прежнему все делает один человек.

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

Вернемся к вашему вопросу.

№ 2: синхронно: всю работу выполняет основной поток. Этот поток возвращается после того, как яйцо закипело, прежде чем вызывающий сможет сделать что-нибудь еще.

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

Третья процедура объявлена асинхронной. Это означает, что как только начинается ожидание яйца, вызывающий абонент может сделать что-то еще, например, поджарить хлеб. Учтите, что при этой работе вся работа выполняется одним потоком.

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

Обычно при правильном использовании async-await вы видите следующее: - Каждая функция, объявленная async, возвращает Task вместо void и Task вместо TResult - Есть только одно исключение: обработчик событий возвращает void вместо Task. - Каждая функция, которая вызывает асинхронную функцию, должна быть объявлена async, иначе использование async-await не очень полезно - После вызова метода async вы можете начать поджаривать хлеб, пока варится яйцо. Когда хлеб поджаривается, вы можете дождаться яйца, или вы можете проверить, готово ли яйцо во время поджаривания, или, возможно, наиболее эффективным было бы дождаться Task.WhenAny, чтобы продолжить готовку яйца или тоста или дождаться Task. WhenAll, когда у вас нет ничего полезного, пока не завершены оба.

Надеюсь, эта аналогия поможет

0
Harald Coppoolse 22 Фев 2016 в 11:31