Thread safety of Task.cancel()

Is Task.cancel() safe to call from multiple threads? Here is a little code snippet describing what I mean:

import Foundation

var count: Int = 0
let lock = NSLock()

func increment() -> Int {
  lock.lock()
  count += 1
  lock.unlock()
  return count
}

let hey = Task {
  try await Task.sleep(nanoseconds: UInt64(1.0*pow(10, 9)))
  print("Hey! \(increment())")
}

print("Hey should have started but not finished... \(increment())")

Task {
  try await Task.sleep(nanoseconds: UInt64(0.4*pow(10, 9)))
  print("Cancel 1 \(increment())")
  hey.cancel()
}

print("Same here... \(increment())")

Task {
  try await Task.sleep(nanoseconds: UInt64(0.4*pow(10, 9)) - 10000)
  print("Cancel 2 \(increment())")
  hey.cancel()
}

Thread.sleep(forTimeInterval: 2.0)

Is it possible for this to violate some internal state? I looked at the source code for Task and the underlying C call for cancel() and it looks like it should be able to be safely called from multiple threads, but is this a guarantee? Furthermore, does the same apply to the cancellation of child Tasks caused by cancelled parent Tasks (e.g. A holds parent Task and child Task, B references child Task, A cancels parent Task at the same time that B cancels the child Task)?

Task cancellation is thread-safe, yes. Are you worried about this for some reason?

Cancellation is safe by itself yes.

But if you’re using withTaskCancellationHandler remember that its handler is invoked concurrently to the running task. But the sendable checking etc will help you get this part right.

Thank you. To answer John_McCall, I was a little worried about having a shared Task reference that could be cancelled from both a CoreBluetooth callback as well as from a button.

1 Like

Ah, gotcha. Yeah, cancelling tasks from multiple threads simultaneously is totally fine.

2 Likes