我试图等待超时事件。 我在函数startAwaitEventWithTimeout后面抽象它。 目前我的代码看起来像这样(包括一些调试输出消息):
let startAwaitEventWithTimeout timeoutMs event = async { Console.WriteLine("Starting AwaitEvent in eventAwaiter") let! eventWaiter = Async.StartChild(Async.AwaitEvent event, timeoutMs) try Console.WriteLine("Awaiting event in eventAwaiter") let! res = eventWaiter return Ok res with :? TimeoutException -> return Error () } |> Async.StartChild这是一个测试:
let testEvent = Event<string>() [<EntryPoint>] let run _ = async { Console.WriteLine("Starting event awaiter in main") let! eventAwaiter = testEvent.Publish |> startAwaitEventWithTimeout 1000 Console.WriteLine("Triggering event") testEvent.Trigger "foo" Console.WriteLine("Awaiting event awaiter in main") let! result = eventAwaiter match result with | Ok str -> Console.WriteLine("ok: " + str) | Error () -> Console.WriteLine("TIMEOUT") } |> Async.RunSynchronously 0不幸的是,尽管我已经看到所有内容都在“等待”,但似乎run函数继续在Async.AwaitEvent有机会订阅事件之前触发事件。 简而言之,这是我得到的输出:
Starting event awaiter in main Starting AwaitEvent in eventAwaiter Triggering event Awaiting event awaiter in main Awaiting event in eventAwaiter TIMEOUT这是我所期望的:
Starting event awaiter in main Starting AwaitEvent in eventAwaiter Awaiting event in eventAwaiter <-- this is moved up Triggering event Awaiting event awaiter in main ok foo我可以通过添加例如do! Async.Sleep 100来解决问题do! Async.Sleep 100 调用startAwaitEventWithTimeout和触发事件之间的do! Async.Sleep 100 ,但当然这不太理想。
我做错了什么,有没有办法可以确保在触发事件之前调用AwaitEvent ?
(旁注:我这样做是因为我们通过TCP调用远程进程,所有来自远程的通信都是通过事件完成的。)
I am trying to await an event with timeout. I am abstracting this behind a function startAwaitEventWithTimeout. Currently my code looks like this (including some debug output messages):
let startAwaitEventWithTimeout timeoutMs event = async { Console.WriteLine("Starting AwaitEvent in eventAwaiter") let! eventWaiter = Async.StartChild(Async.AwaitEvent event, timeoutMs) try Console.WriteLine("Awaiting event in eventAwaiter") let! res = eventWaiter return Ok res with :? TimeoutException -> return Error () } |> Async.StartChildHere's a test:
let testEvent = Event<string>() [<EntryPoint>] let run _ = async { Console.WriteLine("Starting event awaiter in main") let! eventAwaiter = testEvent.Publish |> startAwaitEventWithTimeout 1000 Console.WriteLine("Triggering event") testEvent.Trigger "foo" Console.WriteLine("Awaiting event awaiter in main") let! result = eventAwaiter match result with | Ok str -> Console.WriteLine("ok: " + str) | Error () -> Console.WriteLine("TIMEOUT") } |> Async.RunSynchronously 0Unfortunately, even though everything is "awaited" as far as I can see, it seems the run function proceeds to triggering the event before Async.AwaitEvent has had a chance to subscribe to the event. In short, here is the output I get:
Starting event awaiter in main Starting AwaitEvent in eventAwaiter Triggering event Awaiting event awaiter in main Awaiting event in eventAwaiter TIMEOUTHere is what I would expect:
Starting event awaiter in main Starting AwaitEvent in eventAwaiter Awaiting event in eventAwaiter <-- this is moved up Triggering event Awaiting event awaiter in main ok fooI can work around the problem by adding e.g. do! Async.Sleep 100 between calling startAwaitEventWithTimeout and triggering the event, but of course this is less than ideal.
Have I done something incorrectly, and is there any way I can reliably ensure that AwaitEvent has been called before I trigger the event?
(Side note: I am doing this because we are calling remote processes over TCP, and all communication from the remote is done via events.)
最满意答案
可能我缺少一些要求,但您的代码可以使用continuation轻松重构,并且错误由其自身修复。
let testEvent = Event<unit>() let run _ = let ts = new CancellationTokenSource(TimeSpan.FromSeconds(float 1)) let rc r = Console.WriteLine("ok") let ec _ = Console.WriteLine("exception") let cc _ = Console.WriteLine("cancelled") Async.StartWithContinuations((Async.AwaitEvent testEvent.Publish), rc , ec, cc, ts.Token ) testEvent.Trigger() run()编辑:如果您有使用异步工作流的特定要求,可以使用TPL中的TaskCompletionSource进行转换。
let registerListener timeout event= let tcs = TaskCompletionSource() let ts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)) let er _ = tcs.SetResult (Error()) Async.StartWithContinuations(Async.AwaitEvent event, tcs.SetResult << Ok , er , er , ts.Token) Async.AwaitTask tcs.Task let run _ = let testEvent = Event<int>() async { let listener = registerListener (float 1) testEvent.Publish testEvent.Trigger 2 let! ta = listener match ta with | Ok n -> printfn "ok: %d" n | Error () -> printfn "error" } |> Async.RunSynchronously run()请注意,即使比产生/等待多个子计算更容易理解,但大多数代码仍然是样板,我相信设置简单的超时值必须有更容易的解决方案。
Probably I am missing some requirement but your code can easily be refactored using continuations and the error fixed by itself.
let testEvent = Event<unit>() let run _ = let ts = new CancellationTokenSource(TimeSpan.FromSeconds(float 1)) let rc r = Console.WriteLine("ok") let ec _ = Console.WriteLine("exception") let cc _ = Console.WriteLine("cancelled") Async.StartWithContinuations((Async.AwaitEvent testEvent.Publish), rc , ec, cc, ts.Token ) testEvent.Trigger() run()Edit: If you have a specific requirement to use async workflows, you can convert it by using TaskCompletionSource in TPL.
let registerListener timeout event= let tcs = TaskCompletionSource() let ts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)) let er _ = tcs.SetResult (Error()) Async.StartWithContinuations(Async.AwaitEvent event, tcs.SetResult << Ok , er , er , ts.Token) Async.AwaitTask tcs.Task let run _ = let testEvent = Event<int>() async { let listener = registerListener (float 1) testEvent.Publish testEvent.Trigger 2 let! ta = listener match ta with | Ok n -> printfn "ok: %d" n | Error () -> printfn "error" } |> Async.RunSynchronously run()Note that even though it is far easier to understand than spawning/awaiting multiple child computations, most of this code is still boilerplate and I am sure there must far easier solutions for setting a simple timeout value.
更多推荐
发布评论