F#异步:取消父/子?

时间:2020-02-04 19:57:03

标签: apache-kafka f# confluent-platform f#-async

因此,我们开始:给定Confluent.Kafka IConsumer<>,它将其包装到专用的async CE中,并在不需要取消的情况下使用。这段代码还可以保护自己免受OperationCancelledException的侵害,并运行finally块以确保正常终止使用者。

let private consumeUntiCancelled callback (consumer: IConsumer<'key, 'value>) =
    async {
            let! ct = Async.CancellationToken
            try
                try
                    while not ct.IsCancellationRequested do 
                        let consumeResult = consumer.Consume(ct)
                        if not consumeResult.IsPartitionEOF then do! (callback consumeResult) 
                with
                | :? OperationCanceledException -> return ()
            finally
                consumer.Close()
                consumer.Dispose()
    }

问题1::此代码正确还是我在滥用async

到目前为止一切顺利。在我的应用中,我必须与必须完全死亡的许多消费者打交道。因此,假设consumers: seq<Async<unit>>代表它们,下面的代码就是我想出的:

async {
        for consumer in consumers do 
            do! (Async.StartChild consumer |> Async.Ignore).
    }

我希望此代码将子级链接到父级的取消上下文,并且一旦取消,子级也将被取消。

问题2:即使孩子被取消了,我的finally代码块是否也可以保证运行?

1 个答案:

答案 0 :(得分:1)

关于您的代码,我有两个发现:

  • 您对Async.StartChild的使用是正确的-所有子计算都将继承相同的取消令牌,并且在取消主令牌时,所有子计算都将被取消。

  • 可以在调用consumer.Consume(ct)之后和调用callback之前取消异步工作流。我不确定这对您的特定问题意味着什么,但是如果它从队列中删除了一些数据,则在处理数据之前可能会丢失它们。如果这是一个问题,那么我认为您需要使callback非异步,或以其他方式调用它。

  • consumeUntilCancelled函数中,您不需要显式检查ct.IsCancellationRequested是否为true。异步工作流程会在每个do!let!中自动执行此操作,因此您可以仅使用while循环来替换它。

这是最小的独立演示:

let consume s = async {
  try
    while true do 
      do! Async.Sleep 1000
      printfn "%s did work" s
  finally
    printfn "%s finalized" s }

let work = 
  async {
    for c in ["A"; "B"; "C"; "D"] do  
      do! Async.StartChild (consume c) |> Async.Ignore }

现在,我们使用取消令牌创建计算:

// Run this in F# interactive
let ct = new System.Threading.CancellationTokenSource()
Async.Start(work, ct.Token)

// Run this sometime later
ct.Cancel()

一旦调用ct.Cancel,将调用所有finally块,并且所有循环将停止。