Как работает Async.AwaitTask в f #? - PullRequest
0 голосов
/ 05 июля 2018

Я понимаю, что основное различие между асинхронной моделью f # и c # состоит в том, что в f # асинхронное выполнение не начинается, если вы не вызовете что-то вроде Async.RunSynchronously. В c #, когда метод возвращает задачу, выполнение обычно (не всегда) немедленно начинается в фоновом потоке.

Документация Async.AwaitTask гласит: «Возвращает асинхронные вычисления, которые ожидают завершения заданной задачи и возвращают ее результат».

Означает ли это, что когда вы вызываете метод c #, который возвращает задачу, выполнение уже началось в фоновом режиме? Если так, то какой смысл заключать его в асинхронный тип?

1 Ответ

0 голосов
/ 05 июля 2018

Смысл оборачивания Задачи в Async состоит в том, чтобы упростить его компоновку с другими асинхронами или использовать ее с let! внутри блока async { ... }. И в последнем случае упакованная Задача не будет запущена, пока не будет запущен включающий ее блок async { ... }.

Например, давайте посмотрим на следующую функцию:

let printTask str =
  async {
    printfn "%s" str
  } |> Async.StartAsTask

Это мало что дает; его единственная причина существования заключается в том, что вы можете сказать, когда он начал работать, потому что он выведет сообщение на экран. Если вы звоните из F # Interactive:

printTask "Hello"

Вы увидите следующий вывод:

Hello
val it : Threading.Tasks.Task<unit> =
  System.Threading.Tasks.Task`1[Microsoft.FSharp.Core.Unit]
    {AsyncState = null;
     CreationOptions = None;
     Exception = null;
     Id = 4;
     IsCanceled = false;
     IsCompleted = true;
     IsCompletedSuccessfully = true;
     IsFaulted = false;
     Status = RanToCompletion;}

Таким образом, он напечатал «Hello» и затем вернул завершенное задание Это доказывает, что Задание было начато немедленно.

Но теперь посмотрите на следующий код:

open System.Net
open System
open System.IO

let printTask str =
  async {
    printfn "%s" str
  } |> Async.StartAsTask

let fetchUrlAsync url =
  async {
    let req = WebRequest.Create(Uri(url))
    do! printTask ("started downloading " + url) |> Async.AwaitTask
    use! resp = req.GetResponseAsync() |> Async.AwaitTask
    use stream = resp.GetResponseStream()
    use reader = new IO.StreamReader(stream)
    let html = reader.ReadToEnd()
    do! printTask ("finished downloading " + url) |> Async.AwaitTask
  }

(Это пример Скотта Влашина "Async Web Downloader" , адаптированный для использования задач вместо внутренних асинхронных).

Здесь блок async { ... } содержит три задачи, каждая из которых заключена в Async.AwaitTask. (Обратите внимание, что если вы удалите |> Async.AwaitTask из любой из этих строк, вы получите ошибку типа). Для каждой задачи после выполнения строки кода она начнется сразу же. Но это важный момент, потому что общее вычисление async { ... } составляет , а не началось сразу. Так что я могу сделать:

let a = fetchUrlAsync "http://www.google.com"

И единственное, что напечатано в F # Interactive, это val a : Async<unit>. Я могу ждать столько, сколько захочу, и больше ничего не печатается. Только когда я на самом деле начну a, он начнет работать:

a |> Async.RunSynchronously

Это печатает started downloading http://www.google.com сразу, затем после короткой паузы печатает finished downloading http://www.google.com.

Так что цель Async.AwaitTask: позволить async { ... } блокам более легко взаимодействовать с кодом C #, который возвращает Задачи. И если вызов Async.AwaitTask находится внутри блока async { ... }, то задача на самом деле не запустится, пока не будет запущен включающий Async, поэтому вы по-прежнему получаете все преимущества «холодного запуска» асинхронных операций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...