F# 异步等效于Task.ContinueWith

F# 异步等效于Task.ContinueWith,f#,fody,continuewith,async-workflow,F#,Fody,Continuewith,Async Workflow,我一直在为一些较大的.NET解决方案实现一个[]属性,该属性允许将可配置分析轻松添加到任何被认为重要的函数/方法中。我使用Fody和来截取每个函数的入口和出口,并记录指标。这对于同步函数很有效,对于返回Task的方法,有一个可行的解决方案是使用Task.ContinueWith,但对于F#Async返回函数,MethodBoundaryAspect中的OnExit会在异步返回后立即运行(而不是在实际执行异步时) 为了为F#Async返回函数捕获正确的度量,我试图提出一个使用Task.Contin

我一直在为一些较大的.NET解决方案实现一个
[]
属性,该属性允许将可配置分析轻松添加到任何被认为重要的函数/方法中。我使用Fody和来截取每个函数的入口和出口,并记录指标。这对于同步函数很有效,对于返回
Task
的方法,有一个可行的解决方案是使用
Task.ContinueWith
,但对于F#Async返回函数,MethodBoundaryAspect中的
OnExit
会在异步返回后立即运行(而不是在实际执行异步时)

为了为F#Async返回函数捕获正确的度量,我试图提出一个使用
Task.ContinueWith
的等效解决方案,但我能想到的最接近的事情是创建一个新的异步,绑定第一个,运行度量捕获函数,然后返回原始结果。由于我截取的F#Async返回值仅以
obj
的形式呈现,这一事实进一步复杂化了,此后我必须进行反思,因为没有像
Task
那样的
Async
的非通用版本可以在不知道确切返回类型的情况下使用

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

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

[<AllowNullLiteral>]
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Property, AllowMultiple = false)>]
type TraceAttribute () =
    inherit OnMethodBoundaryAspect()

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

    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 -> // Here's where I could use some help
            let clrType = other.GetType()
            if clrType.IsGenericType && clrType.GetGenericTypeDefinition() = typedefof<Async<_>> then
                // If the return type is an F# Async, replace it with a new Async that calls exit after the original return value is computed
                let returnType = clrType.GetGenericArguments().[0]
                let functionType = FSharpType.MakeFunctionType(returnType, typedefof<Async<_>>.MakeGenericType([| returnType |]))
                let f = FSharpValue.MakeFunction(functionType, (fun _ -> exit(); other))
                let result = typeof<AsyncBuilder>.GetMethod("Bind").MakeGenericMethod([|returnType; returnType|]).Invoke(async, [|other; f|]) 
                args.ReturnValue <- result
            else
                exit()
开放系统
开放系统诊断
打开FSharp。反射
打开MethodBoundaryAspect.Fody.Attributes
[]
[]
类型TraceAttribute()=
继承OnMethodBoundaryAspect()
let-traceEvent(args:MethodExecutionArgs)(时间戳:int64)=
//在这里捕获指标
()
重写.OnEntry(args)=
Stopwatch.GetTimestamp()|>TraceeEvent参数
重写_uxit.OnExit(args)=
let exit()=Stopwatch.GetTimestamp()|>traceEvent参数
将args.ReturnValue与
| :? System.Threading.Tasks.Task作为任务->
task.ContinueWith(乐趣->退出())|>忽略
|其他->//这是我需要帮助的地方
让clrType=other.GetType()
如果clrType.IsGenericType&&clrType.GetGenericTypeDefinition()=typedefof,则
//如果返回类型是F#Async,则将其替换为在计算原始返回值后调用exit的新Async
让returnType=clrType.GetGenericArguments()[0]
让functionType=FSharpType.MakeFunctionType(returnType,typedefof.MakeGenericType([| returnType |]))
设f=FSharpValue.MakeFunction(functionType,(fun->exit();other))
让result=typeof.GetMethod(“Bind”).MakeGenericMethod([| returnType;returnType |]]).Invoke(异步,[|其他;f |])

args.ReturnValue类似的内容可能就是您所需要的:

let traceAsync (a:Async<_>) = async {
    trace() // trace start of async
    let! r = a
    trace() // trace end of async
    return r
}
让traceAsync(a:Async)=Async{
trace()//跟踪异步的开始
设!r=a
trace()//异步的跟踪结束
返回r
}

请考虑,当函数返回异步时,这并不意味着异步已启动。异步更像是一个函数,它可以被调用多次或根本不被调用。这意味着您需要检查
onetry
方法中的返回值是否也是异步的。

根据@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()
开放系统
开放系统诊断
打开FSharp。反射
打开MethodBoundaryAspect.Fody.Attributes
[]
[]
类型TraceAttribute()=
继承OnMethodBoundaryAspect()
静态let AsyncTypeDef=typedefof
静态let Tracer=typeof
静态let AsyncTracer=Tracer.GetMethod(“TraceAsync”)
let-traceEvent(args:MethodExecutionArgs)(时间戳:int64)=
//在这里捕获指标
()
成员_uu.TraceAsync(asyncResult:Async)跟踪=
异步的{
让!result=asyncResult
跟踪()
返回结果
}
重写.OnEntry(args)=
Stopwatch.GetTimestamp()|>TraceeEvent参数
重写_uxit.OnExit(args)=
let exit()=Stopwatch.GetTimestamp()|>traceEvent参数
将args.ReturnValue与
| :? System.Threading.Tasks.Task作为任务->
task.ContinueWith(乐趣->退出())|>忽略
|其他->
让clrType=other.GetType()
如果clrType.IsGenericType&&clrType.GetGenericTypeDefinition()=AsyncTypeDef,则
让泛型=clrType.GetGenericArguments()
让result=AsyncTracer.MakeGenericMethod(泛型).Invoke(this,[| other;exit |])

谢谢,这让我走上了正轨。实际上,我想捕获从调用函数到异步执行完成的整个时间,所以我保留了我的
onetry
行为,但使用这样的函数并在我的
OnExit
中反射性地调用它肯定是一种改进。