且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

正确地在F#中等待具有Task< T>返回类型的异步C#方法.

更新时间:2022-03-11 07:44:26

首先,在您的用例中,不需要async { }块. Async.AwaitTask返回一个Async<'T> ,因此您的async { }块只是将您获取的Async对象展开,然后立即将其重新展开.

First of all, in your use case, there's no need for the async { } block. Async.AwaitTask returns an Async<'T>, so your async { } block is just unwrapping the Async object that you get and immediately re-wrapping it.

现在,我们已经摆脱了不必要的async块,让我们看一下所获得的类型以及您想要获得的类型.您得到一个Async<'a>,并且想要一个类型为'a的对象.浏览可用的Async函数,具有像Async<'a> -> 'a这样的类型签名的是

Now that we've gotten rid of the unnecessary async block, let's look at the type you've gotten, and the type you wanted to get. You got an Async<'a>, and you want an object of type 'a. Looking through the available Async functions, the one that has a type signature like Async<'a> -> 'a is Async.RunSynchronously. It takes two optional parameters, an int and a CancellationToken, but if you leave those out, you've got the function signature you're looking for. And sure enough, once you look at the docs it turns out that Async.RunSynchronously is the F# equivalent of C#'s await, which is what you want. sort of (but not exactly) like C#'s await. C#'s await is a statement you can use inside an async function, whereas F#'s Async.RunSynchronously takes an async object blocks the current thread until that async object has finished running. Which is precisely what you're looking for in this case.

let readEventFromEventStore<'a when 'a : not struct> (eventStore:IEventStoreRepository) (streamName:string) (position:int) =
    eventStore.ReadEventAsync(streamName, position) 
    |> Async.AwaitTask
    |> Async.RunSynchronously

这应该可以为您提供所需的东西.并请注意找出所需功能的功能签名,然后寻找具有该签名的功能的技术.将来会帮助很多.

That should get you what you're looking for. And note that technique of figuring out the function signature of the function you need, then looking for a function with that signature. It'll help a LOT in the future.

更新:感谢Tarmil指出我在注释中的错误:Async.RunSynchronously 等同于C#的await.这非常相似,但是由于RunSynchronously会阻塞当前线程,因此需要注意一些重要的细节. (您不想在GUI线程中调用它.)

Update: Thank you Tarmil for pointing out my mistake in the comments: Async.RunSynchronously is not equivalent to C#'s await. It's pretty similar, but there are some important subtleties to be aware of since RunSynchronously blocks the current thread. (You don't want to call it in your GUI thread.)

更新2::要在不阻塞当前线程的情况下等待异步结果,通常是这样的模式的一部分:

Update 2: When you want to await an async result without blocking the current thread, it's usually part of a pattern that goes like this:

  1. 调用一些异步操作
  2. 等待结果
  3. 根据结果做些事情

编写该模式的***方法如下:

The best way to write that pattern is as follows:

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        doSomethingWith result
    }

以上假设doSomethingWith返回unit,因为您将其称为副作用.相反,如果它返回一个值,则可以执行以下操作:

The above assumes that doSomethingWith returns unit, because you're calling it for its side effects. If instead it returns a value, you'd do:

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        let value = someCalculationWith result
        return value
    }

或者,当然:

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        return (someCalculationWith result)
    }

假定someCalculationWith不是异步操作.如果相反,您需要将两个异步操作链接在一起,而第二个异步操作使用第一个异步操作的结果,或者甚至以某种顺序使用三个或四个异步操作,那么它将看起来像这样:

That assumes that someCalculationWith is NOT an async operation. If instead you need to chain together two async operations, where the second one uses the first one's result -- or even three or four async operations in a sequence of some kind -- then it would look like this:

let equivalentOfAwait () =
    async {
        let! result1 = someAsyncOperation()
        let! result2 = nextOperationWith result1
        let! result3 = penultimateOperationWith result2
        let! finalResult = finalOperationWith result3
        return finalResult
    }

除了let!后跟return完全等同于return!,因此***将其写为:

Except that let! followed by return is exactly equivalent to return!, so that would be better written as:

let equivalentOfAwait () =
    async {
        let! result1 = someAsyncOperation()
        let! result2 = nextOperationWith result1
        let! result3 = penultimateOperationWith result2
        return! (finalOperationWith result3)
    }

所有这些函数将产生一个Async<'T>,其中'T将是async块中最终函数的返回类型.要真正运行这些异步块,您可以像已经提到的那样执行Async.RunSynchronously,或者可以使用各种Async.Start函数之一(StartStartImmediateStartAsTaskStartWithContinuations等). Async.StartImmediate示例会谈关于 Async.SwitchToContext函数,这可能是您要阅读的内容.但是,我对SynchronizationContext的了解还不够多.

All of these functions will produce an Async<'T>, where 'T will be the return type of the final function in the async block. To actually run those async blocks, you'd either do Async.RunSynchronously as already mentioned, or you could use one of the various Async.Start functions (Start, StartImmediate, StartAsTask, StartWithContinuations, and so on). The Async.StartImmediate example talks a little bit about the Async.SwitchToContext function as well, which may be something you'll want to read about. But I'm not familiar enough with SynchronizationContexts to tell you more than that.