It is reasonable to use a semaphore for this, as described here for threads:
I have also seen a Channel used as a adhoc semaphore.
Which is marginally faster since it is not having to take out locks etc.
but probably not worth the loss of clarity for code.
Plus using a semaphore frees you to change from tasks to threads later.
(threads are just tasks without the sticky bit set)