Вернуть запущенную асинхронную<T> в F# функцию из вызова неасинхронной Func<T> из C#?

Допустим, я хочу вызвать из F# эту функцию C#:

public static class Foo {
    public Bar Baz()
    {
       ...
    }
}

Проблема в том, что эта функция сильно загружает процессор, и я не хочу блокировать ее. К сожалению, библиотека C# не имеет Task<Bar> BazAsync() перегрузки.

Затем я хочу сам предоставить асинхронную версию, создав функцию F#, которая вызывает ее и возвращает (уже запущенная) Async<Bar>, То есть я не хочу использовать System.Threading.Task<Bar>,

Я думаю, что я ищу, это эквивалент Task<T>.Run() в F# Async способ делать вещи.

Я уже посмотрел на следующие варианты:

  • Async.StartAsTask -> имеет дело с типом задачи C#ish.
  • Async.Start а также Async.StartImmediately -> получить Async<unit> не Async<T>

Является Async.StartChild что я ищу? Если да, то это будет:

let BazWrapper() =
    let asyncTask: Async<Bar> = async {
        return Foo.Bar()
    }
    Async.StartChild asyncTask

Однако, если это выше, решение:

  1. Почему в большей части документации по асинхронным рабочим процессам F# не упоминается StartChild?
  2. Почему BazWrapper возвращается Async<Async<Bar>> вместо Async<Bar>?

1 ответ

Решение

BazWrapper возвращает Async<Async<Bar>> потому что это то, что делает StartChild: это занимает Async<'T> и возвращается Async<Async<'T>>, Намерение состоит в том, чтобы использовать его в выражении асинхронных вычислений, чтобы вы могли запустить несколько "дочерних" асинхронных операций. Пример из примера кода Async.Start vs Async.StartChild:

async {
    //(...async stuff...)
    for msg in msgs do 
        let! child = asyncSendMsg msg |> Async.StartChild
        ()
    //(...more async stuff...)
}

Когда вы внутри async Вычислительное выражение, let! Ключевое слово будет "развернуть" Async<'Whatever> оставляя вас со значением типа 'Whatever, В случае звонка Async.StartChild, 'Whatever тип конкретно Async<'T>,

Поэтому, если вы хотите вернуть уже запущенную асинхронную передачу через Async.StartChildспособ сделать это будет:

let BazWrapper() =
    let asyncTask: Async<Bar> = async {
        return Foo.Bar()
    }
    async {
        let! child = Async.StartChild asyncTask
        return! child
    }

Однако я подозреваю, что вы обнаружите, что уже запущенная асинхронность не так полезна для вас, как "холодная" асинхронность (еще не запущенная), потому что "холодная" асинхронность все еще может быть составлена ​​с другими асинхронные задачи перед его запуском. (Это может быть полезно, например, для оборачивания журналирования вокруг ваших асинхронных элементов.) Поэтому, если бы я писал код для вашей ситуации, я бы, вероятно, просто сделал это:

let BazWrapper() =
    async {
        return Foo.Bar()
    }

И теперь BazWrapper() возвращает Async, который еще не был запущен, и вы можете запустить его с Async.RunSynchronously если вы хотите сразу получить значение или использовать его в другом async Вычислительные выражения.

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