Почему я должен обернуть Async<T> в другой асинхронный рабочий процесс и позволить! Это?

Я пытаюсь понять асинхронные рабочие процессы в F#, но нашел одну часть, которую я действительно не понимаю, и надеюсь, что кто-то может помочь мне с этим.

Следующий код работает нормально:

let asynWorkflow = async{
    let! result = Stream.TryOpenAsync(partition) |> Async.AwaitTask 
    return result
    } 

let stream = Async.RunSynchronously asynWorkflow
             |> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition)

Я определяю асинхронный рабочий процесс, в котором TryOpenAsync возвращает тип задачи. Я конвертирую его в Async с помощью Async.AwaitTask. (Побочный квест: Задача "Await"? Она не ждет, она просто конвертируется, не так ли? Думаю, она не имеет ничего общего с Task.Wait или ключевым словом await). Я "жду" его с давай! и верни это. Чтобы запустить рабочий процесс, я использую RunSynchronously, который должен запустить рабочий процесс и вернуть результат (связать его). По результату я проверяю, найден ли поток или нет.

Но теперь к моему первому вопросу. Почему я должен обернуть вызов TryOpenAsync в другое асинхронное вычисление и позволить! ("жду") это? Например, следующий код не работает:

let asynWorkflow =  Stream.TryOpenAsync(partition) |> Async.AwaitTask  

let stream = Async.RunSynchronously asynWorkflow
             |> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition)

Я думал, что AwaitTask делает его асинхронным, и RunSynchronously должен запустить его. Тогда используйте результат. Что мне не хватает?

Мой второй вопрос: почему существует какой-либо "Async.Let!" функция доступна? Может быть, потому что он не работает или лучше, почему он не работает со следующим кодом?

let ``let!`` task = async{
    let! result = task |> Async.AwaitTask 
   return result
   } 

let stream = Async.RunSynchronously ( ``let!`` (Stream.TryOpenAsync(partition))  )
         |> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition)

Я просто вставляю TryOpenAsync в качестве параметра, но он не работает. Говоря не работает, я имею в виду, что весь FSI будет зависать. Так что это как-то связано с моей асинхронностью / ожиданием.

--- Обновить:

Результат работы кода в FSI:

>

Real: 00:00:00.051, CPU: 00:00:00.031, GC gen0: 0, gen1: 0, gen2: 0
val asynWorkflow : Async<StreamOpenResult>
val stream : Stream

Результат не работающего кода в FSI:

>

И вы больше не можете выполнять в FSI

--- Обновление 2

Я использую Streamstone. Вот пример C#: https://github.com/yevhen/Streamstone/blob/master/Source/Example/Scenarios/S04_Write_to_stream.cs

а вот Stream.TryOpenAsync: https://github.com/yevhen/Streamstone/blob/master/Source/Streamstone/Stream.Api.cs#L192

3 ответа

Я не могу сказать вам, почему второй пример не работает, не зная, что Stream а также partition есть и как они работают.

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

F# async это своего рода "рецепт" того, что делать. Когда ты пишешь async { ... } результирующие вычисления просто сидят и ничего не делают. Это больше похоже на объявление функции, чем на команду. Только когда вы "начинаете", называя что-то вроде Async.RunSynchronously или же Async.Start это на самом деле работает. Следствием этого является то, что вы можете запускать один и тот же асинхронный рабочий процесс несколько раз, и каждый раз это будет новый рабочий процесс. Очень похоже на то, как IEnumerable работает.

C# Task с другой стороны, это больше похоже на "ссылку" на асинхронное вычисление, которое уже выполняется. Вычисление начинается, как только вы позвоните Stream.TryOpenAsync(partition) и невозможно получить Task Экземпляр до того, как задача фактически начинается. Вы можете await результирующий Task несколько раз, но каждый await не приведет к новой попытке открыть поток. Только первый await будет фактически ждать завершения задачи, и каждый последующий будет просто возвращать вам тот же запомненный результат.

В асинхронном / реактивном жаргоне, F# async это то, что вы называете "холодно", в то время как C# Task упоминается как "горячий".

Второй блок кода выглядит так, как будто он должен работать для меня. Он запускается, если я предоставляю фиктивные реализации для Stream а также StreamOpenResult,

Вы должны избегать использования Async.RunSynchronously где только возможно, потому что это побеждает цель асинхронности. Поместите весь этот код в больший async заблокировать, и тогда у вас будет доступ к StreamOpenResult:

async {
    let! openResult = Stream.TryOpenAsync(partition) |> Async.AwaitTask  
    let stream = if openResult.Found then openResult.Stream else Stream(partition)
    return () // now do something with the stream
    }

Вам может понадобиться поставить Async.Start или же Async.RunSynchronously на самом внешнем краю вашей программы, чтобы на самом деле запустить его, но лучше, если у вас есть async (или преобразовать его в Task) и передать его в другой код (например, веб-фреймворк), который может вызывать его неблокирующим образом.

Не то, чтобы я хотел ответить на ваш вопрос другим вопросом, но: почему вы все равно делаете такой код? Это может помочь понять это. Почему не просто

let asyncWorkflow = async {
    let! result = Stream.TryOpenAsync(partition) |> Async.AwaitTask 
    if result.Found then return openResult.Stream else return Stream(partition) }

Нет смысла создавать асинхронный рабочий процесс только для немедленного вызова RunSynchronously на нем - это похоже на вызов .Result на Task - он просто блокирует текущий поток, пока не вернется рабочий процесс.

Другие вопросы по тегам