Skip to content

Instantly share code, notes, and snippets.

@daveyostcom
Last active November 4, 2023 19:56
Show Gist options
  • Save daveyostcom/72ce3dd8fa00c0d3885d707238aaa3ff to your computer and use it in GitHub Desktop.
Save daveyostcom/72ce3dd8fa00c0d3885d707238aaa3ff to your computer and use it in GitHub Desktop.
SemaphoreSlim WaitAsync with CancellationToken
open System.Threading
open System.Threading.Tasks
let semaphoreWaitAsync semaphore cancellationToken = task {
printfn "Semaphore wait begin."
let ss = (semaphore: SemaphoreSlim)
let ct = (cancellationToken : CancellationToken)
try
do! ss.WaitAsync(ct) // This doesn’t work. OperationCanceledException is not caught.
// ss.WaitAsync(ct) |> Task.WaitAll // This doesn’t work. AggregateException is not caught.
// ss.WaitAsync(ct) |> ignore // This doesn’t work. It returns immediately.
printfn "semaphoreWaitAsync result: true"
return true
with
// | :? AggregateException as ae when ae.InnerExceptions.Count > 0 ->
// ae.InnerExceptions
// |> List.ofSeq
// |> List.map (fun e -> $"Caught exception: {e.Message}")
// |> String.concat "\n"
// |> printfn "semaphoreWaitAsync caught AggregateExceptions:\n%s"
// printfn "semaphoreWaitAsync result: false"
// return false
// | :? OperationCanceledException as e ->
// printfn "semaphoreWaitAsync caught %A" e
// printfn "semaphoreWaitAsync result: false"
// return false
| e ->
printfn "semaphoreWaitAsync caught %A" e
printfn "semaphoreWaitAsync result: false"
return false }
let releaseAfterDelay runSec semaphore =
let worker() =
printfn $"Task started. Waiting for {runSec} seconds before releasing the semaphore..."
task { do! Task.Delay(runSec * 1000) } |> Task.WaitAll
let s = (semaphore: SemaphoreSlim)
s.Release() |> ignore
printfn "releaseAfterDelay done"
Task.Run(worker)
let cancelAfterDelay cancelSec cts =
let worker() =
task { do! Task.Delay(cancelSec * 1000) } |> Task.WaitAll
printfn "Cancelling."
(cts: CancellationTokenSource).Cancel()
printfn "cancelAfterDelay done"
Task.Run(worker)
let runAndCancel runSec cancelSec = task {
let semaphore = new SemaphoreSlim(0, 1)
let cts = new CancellationTokenSource()
let a = releaseAfterDelay runSec semaphore
let b = cancelAfterDelay cancelSec cts
let! ok = semaphoreWaitAsync semaphore cts.Token
let tasks = [| a ; b |]
Task.WaitAll(tasks)
printfn $"runAndCancel: {ok}" }
let waitForever() = task {
let semaphore = new SemaphoreSlim(0, 1)
let cts = new CancellationTokenSource()
let! ok = semaphoreWaitAsync semaphore cts.Token
printfn $"waitForever {ok}" }
[<EntryPoint>]
let main _ =
runAndCancel 4 2 |> Task.WaitAll
printfn ""
runAndCancel 2 4 |> Task.WaitAll
printfn ""
waitForever() |> Task.WaitAll
0
(* Output:
Semaphore wait begin.
Task started. Waiting for 4 seconds before releasing the semaphore...
Cancelling.
cancelAfterDelay done
semaphoreWaitAsync caught System.OperationCanceledException: The operation was canceled.
at System.Threading.CancellationToken.ThrowOperationCanceledException()
at System.Threading.SemaphoreSlim.WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, Int32 millisecondsTimeout, CancellationToken cancellationToken)
at Program.semaphoreWaitAsync@5.MoveNext() in /tmp/SemaphoreWaitAsync/Program.fs:line 10
semaphoreWaitAsync result: false
releaseAfterDelay done
runAndCancel: False
Semaphore wait begin.
Task started. Waiting for 2 seconds before releasing the semaphore...
releaseAfterDelay done
semaphoreWaitAsync result: true
Cancelling.
cancelAfterDelay done
runAndCancel: True
Semaphore wait begin.
*)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment