Above code does not prevent concurrent tasks from entering f1 before the previous task exited. The code only works for two tasks.
When there are two (or more) tasks waiting and one task is executing, the waiting tasks all await the same condition. So all of them can enter when the condition is met.
It depends on how "low level" you want to drop in the Concurrency toolbox, but if you only want to play with tasks, the following will ensure sequential execution
It does capture, yes, but that is intentional to ensure sequential execution. Note at each call to f2() will only capture the latest task and it will be cleaned up once that call to f2() finishes so the "linked list" will not grow forever.
Any kind of solution to this problem, as it is stated, will require memory allocation, so I wouldn't characterize this as a memory leak. If f2() is called very very often and very very frequently, then the problem ought to be solved in a different way in the first place (instead of using the Car actor etc.)
Of course when using a semaphore you need to be careful to not introduce "deadlocks" when one function acquires it after it has already been acquired resulting in an indefinite suspension.
and it will be cleaned up once that call to f2() finishes
You are right, thank you!
I was under the wrong assumption that the task would retain the closure as long as a Task handle is around. But this is not the case, it is released as soon as the task finishes execution.