大規模な .NET ソリューションの一部に属性を実装してい[<Trace>]
ます。これにより、構成可能な分析を、重要と見なされる関数/メソッドに簡単に追加できるようになります。Fody とMethodBoundaryAspectを使用して、各関数の入口と出口をインターセプトし、メトリックを記録しています。これは、同期関数に対してはうまく機能し、メソッドが を返すTask
場合は で実行可能な解決策がありますTask.ContinueWith
が、F# Async を返す関数のOnExit
場合、Async が実際に実行されるときではなく、Async が返されるとすぐに MethodBoundaryAspect の が実行されます。
F# Async を返す関数の正しいメトリクスを取得するために、 を使用するのと同等のソリューションを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()
残念ながら、この解決策は非常に厄介であるだけでなく、特にループで呼び出された関数や深くネストされた関数をトレースしようとしているときに、非同期計算のリフレクション構造により、自明ではない量のオーバーヘッドが追加されていると思います。非同期呼び出し。非同期計算が実際に評価された直後に、特定の関数を実行するのと同じ結果を達成するためのより良い方法はありますか?