[英]How to write a Task.ContinueWith within an Async computation expression
[英]F# Async Equivalent of Task.ContinueWith
我一直在為我們的一些較大的 .NET 解決方案實施[<Trace>]
屬性,這將允許將可配置分析輕松添加到任何被認為重要的函數/方法。 我正在使用Fody和MethodBoundaryAspect來攔截每個函數的進入和退出並記錄指標。 這適用於同步函數,對於返回Task
方法,有一個可行的解決方案Task.ContinueWith
,但對於 F# 異步返回函數, MethodBoundaryAspect 的OnExit
在 Async 返回后立即運行(而不是在 Async實際執行)。
為了捕獲 F# 異步返回函數的正確指標,我試圖想出一個等效的解決方案來使用Task.ContinueWith
,但我能想到的最接近的事情是創建一個新的 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()
不幸的是,這個解決方案不僅非常混亂,而且我相信異步計算的反射構造增加了大量的開銷,尤其是當我試圖跟蹤在循環中調用或嵌套很深的函數時異步調用。 是否有更好的方法可以在實際評估異步計算后立即運行給定函數獲得相同的結果?
像這樣的東西可能是你需要的:
let traceAsync (a:Async<_>) = async {
trace() // trace start of async
let! r = a
trace() // trace end of async
return r
}
考慮當一個函數返回一個異步時並不意味着異步已經開始。 異步更像是一個函數,它可以被調用多次或根本不調用。 這意味着您需要在OnEntry
方法中檢查返回值是否也是 Async。
按照@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
實現保持不變。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.