F# 异步等效于 Task.ContinueWith



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

为了捕获 F# 异步返回函数的正确指标,我试图提出一个与使用Task.ContinueWith等效的解决方案,但我能想到的最接近的事情是创建一个新的异步,绑定第一个异步,运行指标捕获函数,然后返回原始结果。 由于我截获的 F# 异步返回值仅显示为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()

不幸的是,这个解决方案不仅非常混乱,而且我相信异步计算的反射结构增加了大量的开销,特别是当我尝试跟踪在循环中调用或具有深度嵌套的异步调用的函数时。 有没有更好的方法可以在实际评估异步计算后立即运行给定函数的相同结果?

像这样的东西可能是你需要的:

let traceAsync (a:Async<_>) = async {
trace() // trace start of async
let! r = a
trace() // trace end of async
return r
}

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

按照@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()

这似乎可以正确跟踪异步函数,开销要少得多。 我确实想跟踪从调用函数到异步实际启动的总时间,所以我让我的OnEntry实现保持不变。

相关内容

  • 没有找到相关文章

最新更新