Implement an asynchronous task queue with concurrency limit: tasks are first added to the queue, then started by the loop method; no more than a specified number of tasks should run simultaneously. When all queued and running tasks are finished, notify about queue completion.