F#异步的Task.ContinueWith

时间:2019-03-21 19:54:13

标签: f# fody continuewith async-workflow

我一直在为一些较大的.NET解决方案实现[<Trace>]属性,该属性将使可配置分析易于添加到任何重要的函数/方法中。我正在使用Fody和MethodBoundaryAspect来拦截每个函数的进入和退出并记录指标。这对于同步函数非常有用,对于返回Task的方法,有一个Task.ContinueWith可行的解决方案,但是对于F#异步返回函数,MethodBoundaryAspect中的OnExit会尽快运行返回异步(而不是实际执行异步时)。

为了为F#异步返回功能捕获正确的指标,我试图提出一种与使用Task.ContinueWith等效的解决方案,但是我能想到的最接近的方法是创建一个新的Async来绑定第一个,运行度量捕获功能,然后返回原始结果。由于我截取的F#异步返回值仅以obj的形式显示,因此,这又使情况变得更加复杂,此后我必须反思地进行所有操作,因为没有{{1}的非通用版本}就像Async一样,我可以在不知道确切的返回类型的情况下使用它。

到目前为止,我最好的解决方案大致如下:

Task

不幸的是,该解决方案不仅非常凌乱,而且我相信异步计算的反射结构会增加大量的开销,尤其是当我尝试跟踪在循环中调用或具有深度嵌套的异步调用。在实际计算了异步计算后,是否有更好的方法立即获得运行给定功能的相同结果?

2 个答案:

答案 0 :(得分:1)

您可能需要这样的东西

cmake --help

请考虑当函数返回异步并不意味着异步已经启动时。异步更像是一个函数,它可以被调用多次或根本不被调用。这意味着您还需要在<configuration> <webXml>WEB-INF/glassfish-web.xml</webXml> </configuration> 方法中检查返回值是否为Async。

答案 1 :(得分:0)

按照@AMieres的建议,我能够更新我的OnExit方法来正确地跟踪异步执行而没有太多的开销。我认为问题的大部分实际上是使用AsyncBuilder的相同实例,这导致了异步函数的额外调用。这是新的解决方案:

open System
open System.Diagnostics
open FSharp.Reflection
open MethodBoundaryAspect.Fody.Attributes

[<AllowNullLiteral>]
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Property, AllowMultiple = false)>]
type TraceAttribute () =
    inherit OnMethodBoundaryAspect()
    static let AsyncTypeDef = typedefof<Async<_>>
    static let Tracer = typeof<TraceAttribute>
    static let AsyncTracer = Tracer.GetMethod("TraceAsync")

    let traceEvent (args: MethodExecutionArgs) (timestamp: int64) =
        // Capture metrics here
        ()

    member __.TraceAsync (asyncResult: Async<_>) trace =
        async {
            let! result = asyncResult
            trace()
            return result
        }

    override __.OnEntry (args) =
        Stopwatch.GetTimestamp() |> traceEvent args

    override __.OnExit (args) =
        let exit () = Stopwatch.GetTimestamp() |> traceEvent args
        match args.ReturnValue with
        | :? System.Threading.Tasks.Task as task ->
            task.ContinueWith(fun _ -> exit()) |> ignore             
        | other -> 
            let clrType = other.GetType()
            if clrType.IsGenericType && clrType.GetGenericTypeDefinition() = AsyncTypeDef then
                let generics = clrType.GetGenericArguments()
                let result = AsyncTracer.MakeGenericMethod(generics).Invoke(this, [| other; exit |])
                args.ReturnValue <- result
            else
                exit()

这似乎可以用更少的开销正确地跟踪Async函数。我确实想跟踪从调用函数起而不是从异步实际开始起的总时间,所以我将OnEntry实现保留为相同。