Как создать асинхронную версию синхронной функции в F #?
Каковы различные методы, которые вы можете использовать для создания экземпляров Async < 'T > в F #?
Я вижу, что существует несколько методов расширения для веб-клиента/запроса и потока файлов, но если я хочу написать собственный поставщик асинхронных вычислений, как бы я начал писать эти версии AsyncDoSomething
моего синхронного DoSomething
функции?
Я знаю, что вы можете использовать делегат одной и той же сигнатуры для обертывания исходной функции, а затем использовать Async.FromBeginEnd
в методах BeginInvoke
и EndInvoke
:
open System
let del : Func<int> = new Func<int>(fun () -> 42)
let delAsync = async {
let! res = Async.FromBeginEnd(del.BeginInvoke, del.EndInvoke)
printfn "result was %d" res
}
Async.Start delAsync
Но это немного напряжено, и это не похоже на "путь F #", поскольку вам нужно использовать делегатов, определенных в С# или VB (из которых есть много вариантов System.Action
и System.Func
, чтобы выбрать конечно), потому что делегаты F # не поддерживают методы BeginInvoke
и EndInvoke
.
Есть ли у кого-нибудь список различных способов записи асинхронной версии синхронной функции в F #?
Большое спасибо заранее!
Ответы
Ответ 1
Из docs для Async
всех методов AwaitXXX
и FromXXX
. Но наиболее распространенным способом является асинхронные рабочие процессы. Однако, как заметил Маурисио, упаковка произвольного кода с помощью async { }
не всегда выгодна.
UPDATE
Здесь немного кода, чтобы продемонстрировать эту точку.
open System.IO
let BUF_SIZE = 1 <<< 16 //64KB
let readFile f (stream:Stream) =
let buf = Array.zeroCreate BUF_SIZE
let rec read p =
async {
let! n = f stream buf
match n with
| 0 -> ()
| _ -> return! read (p + n)
}
read 0
let fakeAsyncReadFile s = readFile (fun stream buf ->
async { return stream.Read(buf, 0, buf.Length) }) s
let realAsyncReadFile s = readFile (fun stream buf ->
stream.AsyncRead(buf, 0, buf.Length)) s
let files = [@"C:\big_file_1"; @"C:\big_file_2"]
let readWith f =
let streams = Seq.map File.OpenRead files
try Seq.map f streams |> Async.Parallel |> Async.RunSynchronously |> ignore
finally streams |> Seq.iter (fun s -> s.Close())
readWith fakeAsyncReadFile //Real: 00:00:34.190, CPU: 00:00:03.166, GC gen0: 4, gen1: 2, gen2: 1
readWith realAsyncReadFile //Real: 00:00:05.101, CPU: 00:00:16.957, GC gen0: 31, gen1: 1, gen2: 0
Обтекание синхронного Stream.Read
с async { }
не дает никаких видимых преимуществ. Асинхронный рабочий процесс - это, в первую очередь, удобный способ цепочки асинхронных операций. То есть, это зависит от наличия хорошо написанных асинхронных операций для начала, чтобы служить в качестве строительных блоков.
Ответ 2
Вы можете получить хороший пробег от Async.FromContinuations. Эта функция позволяет вам определить Async из функций продолжения. Если вы посмотрите на определение WebClient AsyncDownloadString, вы увидите, что это используется.
Ответ 3
И вы можете написать вот так:
let doAsyncTask (f : unit->'a) =
async { return! Task<'a>.Factory.StartNew( new Func<'a>(f) ) |> Async.AwaitTask }
и используйте его как
let asyncFunc arg = async { return! doAsyncTask( fun () -> syncFunc arg ) }