Asynchronous 如何确保Async.StartChild在继续之前已启动?

Asynchronous 如何确保Async.StartChild在继续之前已启动?,asynchronous,f#,async-await,Asynchronous,F#,Async Await,我正在尝试等待超时的事件。我将其抽象为一个函数startAwaitEventWithTimeout。当前我的代码如下所示(包括一些调试输出消息): 这里有一个测试: let testEvent = Event<string>() [<EntryPoint>] let run _ = async { Console.WriteLine("Starting event awaiter in main") let! eventAwaiter = testE

我正在尝试等待超时的事件。我将其抽象为一个函数
startAwaitEventWithTimeout
。当前我的代码如下所示(包括一些调试输出消息):

这里有一个测试:

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
以下是我所期望的:

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
在主菜单中启动事件等待器
正在EventWaiter中启动WaiteEvent

在eventwaiter中等待事件可能我缺少了一些要求,但是您的代码可以使用continuations进行重构,错误可以自行修复

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()
let testEvent=Event()
让我们跑吧=
设ts=新的CancellationTokenSource(TimeSpan.FromSeconds(浮点1))
让rc r=Console.WriteLine(“ok”)
让ec=控制台.WriteLine(“异常”)
让cc u=Console.WriteLine(“已取消”)
Async.StartWithContinuations((Async.AwaitEvent testEvent.Publish)、rc、ec、cc、ts.Token)
testEvent.Trigger()
运行()
编辑:如果您有使用异步工作流的特定需求,可以通过在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()
let registerListener超时事件=
设tcs=TaskCompletionSource()
让ts=新的CancellationTokenSource(TimeSpan.FromSeconds(超时))
让er u=tcs.SetResult(Error())
Async.StartWithContinuations(Async.AwaitEvent事件,tcs.SetResult打印fn“确定:%d”n
|错误()->printfn“错误”
}|>Async.RunSynchronously
运行()

请注意,尽管这比生成/等待多个子计算要容易理解得多,但这段代码中的大部分仍然是样板代码,我相信一定有更简单的解决方案来设置一个简单的超时值。

我认为您不会遇到争用条件,因为您总是在子计算之前触发事件计算甚至已经开始了。让我们改变设置——就像你在测试中所做的那样——在发射前加入一个延迟

open System
open System.Threading

let e = Event<_>()

let sleeper timeToFire = async{
    do! Async.Sleep timeToFire
    e.Trigger() }

let waiter = async{
    do! Async.AwaitEvent e.Publish
    return Ok() }

let foo timeToFire timeOut = async{
    Async.Start(sleeper timeToFire)
    let! child = Async.StartChild(waiter, timeOut)
    try return! child
    with :? TimeoutException -> return Error() }

foo 500 1000 |> Async.RunSynchronously
// val it : Result<unit,unit> = Ok null
foo 1000 500 |> Async.RunSynchronously
// val it : Result<unit,unit> = Error null
开放系统
开放系统。线程
设e=Event()
让睡眠者timeToFire=异步{
do!异步。睡眠时间触发
e、 触发器()}
让服务员=异步{
do!Async.e.发布
返回Ok()}
让foo timeToFire timeOut=async{
异步启动(睡眠时间点火)
let!child=Async.StartChild(服务员,超时)
试着回来!孩子
使用:?TimeoutException->return Error()}
foo 500 1000 |>Async.RunSynchronously
//val it:Result=Ok null
foo 1000 500 |>Async.RunSynchronously
//val it:Result=Error null

如果触发延迟等于超时,则会出现竞态条件。

我需要获取触发事件的值,而
StartWithContinuations
返回
unit
。我稍微修改了问题中的代码以使其更清晰。@cmeeren您可以使用
rc
回调来获取t的值事件。您还有其他要求吗?我在想如何将其集成到现有代码中时遇到问题。假设我有一个异步CE,我想首先设置一个事件等待器,然后触发一个操作,导致事件在某个点被触发,然后将事件值返回给调用代码(当然,它也是
异步的
)(其中
错误()
表示超时)。@cmeeren您可以使用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()
open System
open System.Threading

let e = Event<_>()

let sleeper timeToFire = async{
    do! Async.Sleep timeToFire
    e.Trigger() }

let waiter = async{
    do! Async.AwaitEvent e.Publish
    return Ok() }

let foo timeToFire timeOut = async{
    Async.Start(sleeper timeToFire)
    let! child = Async.StartChild(waiter, timeOut)
    try return! child
    with :? TimeoutException -> return Error() }

foo 500 1000 |> Async.RunSynchronously
// val it : Result<unit,unit> = Ok null
foo 1000 500 |> Async.RunSynchronously
// val it : Result<unit,unit> = Error null