I wanted to comment on the answer to the war, but answering my own question seems like a longer explanation. This may not be the complete answer, sorry.
A military solution is a possible way to implement this function, and I did something similar to this. Basically, create a task repository, and whenever a new task starts or ends, add / remove this task to the task repository. However, I would like to mention some problems, because I think that it is not as simple as that.
1) I agree with using the dependency injection framework to implement a singleton pattern. For this, I used the SingleInstance () method for Autofac. However, as you can see in many answers and resources on the Internet, a singleton pattern is not popular, although it sometimes seems necessary.
2) Your repository should be thread safe, so adding / removing tasks from this task store should be thread safe. You can use a parallel data structure, such as "ConcurrentDictionary", instead of locking (which is an expensive operation in terms of CPU time).
3) You can use the CancellationToken for your asynchronous use. operations. It is possible that your application may be disabled by the user or an exception that may be handled may occur at run time. Long-term tasks can be a bottleneck for elegant shutdown / restart operations, especially if you have sensitive data to lose and use various resources, such as files, that can be closed properly in this case. Although asynchronous. methods cannot be immediately canceled whenever you try to cancel a task using a token, it is still worth trying a CancellationToken or similar mechanisms to stop / cancel your lengthy tasks when necessary.
4) Just adding a new task to the data warehouse is not enough, you also deleted the task, even if it completed successfully or not. I tried to fire an event that signals the completion of a task, and then I deleted this task from the task store. However, I am not happy with this because dismissing the event and then attaching the method to this event to get the task to complete is more or less like a continuation in TPL. This is how to reinvent the wheel, as well as events can cause hidden problems in a multi-threaded environment.
Hope this helps. I prefer the exchange of experience rather than the publication of code, because I do not think that I have a final solution to this problem. The answer to the war gives an approximate idea, but before you do this in a production-ready system, you need to consider many problems.
Edit: For long-running tasks, you can take a look at this thread. It is good practice to manage a thread pool.