Как асинхронный w/ждущий отличается от синхронного вызова?
Я читал об асинхронных вызовах функций на http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx?cs-save-lang=1&cs-lang=csharp.
В первом примере они делают это, и я получаю:
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
string urlContents = await getStringTask;
Но тогда они объясняют, что если не будет никакой работы, которая будет сделана в среднем, вы можете просто сделать это следующим образом:
string urlContents = await client.GetStringAsync();
Из того, что я понимаю, ключевое слово await
приостанавливает поток кода до тех пор, пока функция не вернется. Итак, как это отличается от:
string urlContents = client.GetString();
?
Ответы
Ответ 1
Вызов await client.GetStringAsync()
дает выполнение вызывающему методу, что означает, что он не будет ждать завершения процесса и, следовательно, не будет блокировать поток. Как только это будет выполнено в фоновом режиме, метод продолжит работу с того места, где он остановился.
Если вы просто вызываете client.GetString()
, выполнение потока не будет продолжаться до тех пор, пока этот метод не завершит выполнение, что заблокирует поток и может привести к тому, что пользовательский интерфейс перестанет отвечать.
Пример:
public void MainFunc()
{
InnerFunc();
Console.WriteLine("InnerFunc finished");
}
public void InnerFunc()
{
// This causes InnerFunc to return execution to MainFunc,
// which will display "InnerFunc finished" immediately.
string urlContents = await client.GetStringAsync();
// Do stuff with urlContents
}
public void InnerFunc()
{
// "InnerFunc finished" will only be displayed when InnerFunc returns,
// which may take a while since GetString is a costly call.
string urlContents = client.GetString();
// Do stuff with urlContents
}
Ответ 2
Из того, что я понимаю, ключевое слово await приостанавливает поток кода, пока функция не вернет
Ну, да и нет.
- Да, потому что поток кода в каком-то смысле останавливается.
- Нет, потому что поток, выполняющий этот поток кода, не блокируется. (Синхронный вызов
client.GetString()
заблокирует поток).
Фактически, он вернется к своему вызывающему методу. Чтобы понять, что это значит, вернувшись к его вызывающему методу, вы можете прочитать о другом мастере компилятора С# - инструкции yield return
.
Блоки Iterator с yield return
будут разбивать метод на конечный автомат, где код после оператора yield return
будет выполняться только после вызова MoveNext()
в перечислителе. (Смотрите this и this).
Теперь механизм async/await
также основан на аналогичной машине состояний (однако, это намного сложнее, чем машина состояния yield return
).
Чтобы упростить дело, рассмотрим простой метод async:
public async Task MyMethodAsync()
{
// code block 1 - code before await
// await stateement
var r = await SomeAwaitableMethodAsync();
// code block 2 - code after await
}
- Когда вы отмечаете метод с идентификатором
async
, вы говорите компилятору разбить метод на конечный автомат и что вы собираетесь await
внутри этого метода.
- Допустим, что код работает в потоке
Thread1
, и ваш код вызывает этот MyMethodAsync()
. Затем code block 1
будет синхронно работать в одном потоке.
-
SomeAwaitableMethodAsync()
также будет вызываться синхронно, но позволяет сказать, что метод запускает новую асинхронную операцию и возвращает Task
.
- Это когда
await
входит в изображение. Он вернет поток кода обратно своему вызывающему, и поток Thread1
может свободно запускать код вызывающих абонентов. То, что происходит тогда в методе вызова, зависит от того, является ли метод вызова await
на MyMethodAsync()
или что-то еще, но важная вещь Thread1
не заблокирована.
- Теперь остальная магия ожидания - Когда задача, возвращаемая
SomeAwaitableMethodAsync()
, в конечном итоге завершается, code block 2
запускается по расписанию.
-
async/await
построен на параллельной библиотеке задач - поэтому это планирование выполняется по TPL.
- Теперь дело в том, что этот
code block 2
не может быть запланирован по тому же потоку Thread1
, если только он не имеет активного SynchronizationContext
с аффинностью потока (например, поток пользовательского интерфейса WPF/WinForms). await
имеет значение SynchronizationContext
, поэтому code block 2
запланирован на тот же SynchronizationContext
, если он есть, когда был вызван MyMethodAsync()
. Если активного SynchronizationContext
не было, то при любой возможности code block 2
будет работать через несколько потоков.
Наконец, я скажу, что поскольку async/await
основан на машине состояний, созданной компилятором, например, yield return
, он разделяет некоторые из недостатков - например, вы не можете await
внутри блока finally
.
Надеюсь, это уберет ваши сомнения.