Почему я должен обернуть 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
- он просто блокирует текущий поток, пока не вернется рабочий процесс.