Your counter decision is acceptable. No matter what you do, you will need to keep track of all your requests and understand when they will arrive (when the counter reaches zero).
You can do different things to clear your code, for example, put all this implementation in some class MultiAsyncWaiter, which returns an event upon completion. But the fundamental implication will remain the same: keep an eye on them until they return.
You are right about the insecurity of a stream in int. If you use blocked operations (see Comments) or block this variable, you can keep the implementation stream safe.
: , , , + . , .