我一直在[<Trace>]
为我们的一些大型 .NET 解决方案实现一个属性,该属性将允许将可配置分析轻松添加到任何被认为重要的功能/方法中。我正在使用 Fody 和MethodBoundaryAspect来拦截每个函数的进入和退出并记录指标。这对于同步函数很有效,对于返回的方法,Task
有一个可行的解决方案Task.ContinueWith
,但对于 F# Async-returning 函数,OnExit
来自 MethodBoundaryAspect 的 将在 Async 返回后立即运行(而不是在实际执行 Async 时)。
为了捕获 F# Async-returning 函数的正确指标,我试图想出一个等效的解决方案 using Task.ContinueWith
,但我能想到的最接近的事情是创建一个绑定第一个 Async 的新 Async ,运行指标-捕获函数,然后返回原始结果。由于我截获的 F# Async 返回值仅以obj
.知道确切的返回类型。Async
Task
到目前为止,我最好的解决方案大致如下:
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()
不幸的是,这个解决方案不仅非常混乱,而且我相信异步计算的反射结构增加了不小的开销,特别是当我试图跟踪在循环中调用或深度嵌套的函数时异步调用。是否有更好的方法可以在实际评估异步计算后立即运行给定函数的相同结果?