Я реализовал атрибут [<Trace>]
для некоторых наших более крупных решений .NET, который позволит легко добавлять настраиваемую аналитику к любым функциям/методам, которые считаются важными. Я использую Fody и MethodBoundaryAspect для перехвата входа и выхода каждой функции и записи показателей. . Это хорошо работает для синхронных функций, а для методов, возвращающих Task
, есть рабочее решение с Task.ContinueWith
, но для функций F#, возвращающих Async, OnExit
из MethodBoundaryAspect запускается, как только возвращается Async (а не тогда, когда Async фактически выполняется). казнен).
Чтобы получить правильные метрики для функций F#, возвращающих асинхронный режим, я пытался придумать решение, эквивалентное использованию Task.ContinueWith
, но самое близкое, что я мог придумать, это создать новый асинхронный объект, который связывает первый, запускает метрику -захват функций, а затем возвращает исходный результат. Это еще более усложняется тем фактом, что возвращаемое значение 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()
К сожалению, это решение не только довольно запутанно, но я считаю, что рефлективная конструкция асинхронных вычислений добавляет нетривиальное количество накладных расходов, особенно когда я пытаюсь отследить функции, которые вызываются в цикле или имеют глубоко вложенные функции. Асинхронные вызовы. Есть ли лучший способ добиться того же результата запуска данной функции сразу после фактической оценки асинхронного вычисления?