2016-02-22 2 views
5

Я смотрю на какое-то асинхронное программирование на 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 возвращается? Что в этом случае эта функция будет выглядеть глупо?

Я прав, или я в отъезде?

+1

Первый работает асинхронно, второй не блокирует и блокирует. Результат возвращается синхронно, завернутый в Задачу. Третий почти такой же, как первый - 'async/await' просто упрощает процесс очистки, он не делает ничего асинхронным. Третий фактически имеет некоторый ненужный рывок, потому что результат фактически не используется внутри функции –

+0

PS. Я не отправляю это как ответ, потому что есть много подобных вопросов. –

+1

Примечание: это «асинхронная синхронизация»: бэкэнд-сервис ('GetBar') является в основном синхронным API, и вы открываете его через 'async'. Единственная причина, по которой это делается, заключается в том, чтобы освободить пользовательский интерфейс для рисования. Он не достигает более широкого набора целей «async»: у вас все еще есть ** ** нить, блокирующая результат - это просто не * исходный * поток. –

ответ

6

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

Сказав, что, вот разница:

# 1

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

# 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, они получат тупиковый по причинам, рассмотренным here.

+0

Это был только экзамен, и, скорее всего, будет выполнен код, завершенный в Task.Run(). Если бы я интерпретировал статью, которую вы ссылались на тупик, появился бы только в том случае, если бы я сделал: var bar = GetBar (fooId); bar.Result; Если я делаю: var bar = ждет GetBar (fooId); в асинхронном методе это не произойдет? – Gralov

+1

@ Gralov, правильно, '' ждет 'Задача ', созданная методами 1 и 3, в порядке. 'await'ing' Task ', созданный методом # 2, также * технически * возможно, но не является разумным вообще, поскольку основная часть его будет выполняться синхронно, даже если она будет ожидаться (поэтому вы получаете все связанные с производительностью служебные сообщения с 'async', и ни одно из преимуществ). –

0

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

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

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

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

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

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

Назад к вопросу.

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

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

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

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

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

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

+0

# 1 описание неверно. Этот вариант выполняет * наименьшее количество операций синхронно (по «основному» потоку) перед возвратом «Задачи » вызывающему абоненту (в этот момент «основной» поток может свободно делать что-то еще). В # 3 раздел метода, который гарантированно работает синхронно, немного толще: вы все равно будете иметь вызов 'Task.Run', происходящий в« основном »потоке, но также * также *, за которым следуют все ожидаемые, («GetAwaiter», проверка «IsCompleted», продолжение расписания), также в «основном» потоке, прежде чем 'Task ' наконец возвращается вызывающему. –

+0

Аналог завтрака предлагает ввести многопоточность, которая не является (основной) целью асинхронного ожидания. – MEMark

Смежные вопросы