Cancel workflow subblock a # a

I am trying to create an asynchronous workflow where there is a main async loop that executes an asynchronous sub-block in every loop. And I want this asynchronous sub-block to be canceled, but when it is canceled I don't want the main loop to be canceled. I want it to continue, on the line after do! subBlock

.

The only method I can see in Async

that even has an acceptable signature (accepts CancellationToken

, returns what can be converted to Async

) Async.StartAsTask

, but it looks like it hangs when canceled; at the bottom, it prints "canceled" and then nothing else.

open System
open System.Threading
open System.Threading.Tasks

// runs until cancelled
let subBlock = 
  async { 
    try 
      while true do
        printfn "doing it"
        do! Async.Sleep 1000
        printfn "did it"
    finally
      printfn "cancelled!"
  }

[<EntryPoint>]
let main argv = 
  let ctsRef = ref <| new CancellationTokenSource()      

  let mainBlock = 
    //calls subBlock in a loop
    async { 
      while true do
        ctsRef := new CancellationTokenSource()
        do! Async.StartAsTask(subBlock, TaskCreationOptions.None, (!ctsRef).Token) 
            |> Async.AwaitTask
        printfn "restarting"
    }
  Async.Start mainBlock

  //loop to cancel CTS at each keypress
  while true do
    Console.ReadLine() |> ignore
    (!ctsRef).Cancel()
  0

      

Is there a way to do this?

+3


source to share


2 answers


What happens is that when your child task is canceled, it OperationCanceledException

also resets yours mainBlock

. I was able to get it to work using this:

let rec mainBlock = 
    async {
        ctsRef := new CancellationTokenSource()
        let task = Async.StartAsTask(subBlock, TaskCreationOptions.None, (!ctsRef).Token) |> Async.AwaitTask
        do! Async.TryCancelled(task, fun e -> 
            (!ctsRef).Dispose() 
            printfn "restarting" 
            Async.Start mainBlock)
    }

      



When the task is canceled, it is mainBlock

explicitly restarted in the cancellation handler. You need to add #nowarn "40"

for it as it is used in its definition mainBlock

. Also note that expose the source to the token.

More information on this issue (and perhaps a nicer form solution StartCatchCancellation

) can be found in these two threads .

+1


source


Whether the caller who starts and cancels the job is asynchronous does not affect this issue either, since the worker is controlled through an explicitly specified cancellation token.

Asyncs have three permutations: normal, which can return a value, one for exceptions, and one for cancellation. There are several ways to add continue undo in async, for example Async.OnCancel

, Async.TryCancelled

or a general Async.FromContinuations

one that includes an exception case. Here's a program that has the desired output:

let rec doBlocks () = 
    async { printfn "doing it"
            do! Async.Sleep 1000
            printfn "did it"
            do! doBlocks () }

let rec runMain () =
    use cts = new CancellationTokenSource()
    let worker = Async.TryCancelled(doBlocks (), fun _ -> printfn "Cancelled")
    Async.Start(worker, cts.Token)
    let k = Console.ReadKey(true)
    cts.Cancel()
    if k.Key <> ConsoleKey.Q then runMain ()

      



This works just as well if it runMain

is asynchronous. In this simple case, you can also just print the "canceled" message.

Hope this helps. I don't think there is a general answer to how to structure a program; it depends on the specific use case.

+1


source







All Articles