Search code examples
c#multithreadingdependency-injectionasp.net-core

Dependency injected services used in a new task


I'm using dependency injection to access my services when needed but i'm now wanting to create a concurrent task but this is causing issues due to dependency injected objects and their lifetimes.

I've read this article (specifically the section Prevents multi-threading): Prevents multi-threading And from that, I've gathered that this isn't possible.

My objective is to let the client send a request to begin a job that could take longer than the connection timeout of the client, thus I want to have an endpoint that initiates a task and returns the status of whether the task had successfully started.

The problem is that because the database context and other services are created on the controller within the thread of the request, when you pass that object into a new task and end the old task, the objects are disposed due to the fact that they were created on said thread.

I'm aware that for a database context you can inject the database factory interface and create a new instance but this doesn't help me for the non database objects. (I've also read that creating your own service instances defeats the point of dependency injection).

Is there a way I can create a new instance of my injected dependencies on a new thread/task, or avoid this problem completely? Thanks.


Solution

  • create a concurrent task but this is causing issues due to dependency injected objects and their lifetimes.

    Every time you spin off a background thread, or a task that can potentially run on a different thread in parallel, the first thing you need to do during that operation is request the service you wish to use from the container. This way the container can determine which objects need to be build depending on their lifestyles. Moving dependencies from one thread to another parallel-running thread is a sin.

    Take a look at the following information to understand how to work with DI in multi-threaded applications. The information is written with a particular DI container in mind, but most of it is applicable to DI in general.

    In your case, you wish to forward data of a request to a class that is able to start a new thread, create a new scope, resolve the real handler from that scope and invoke it. You can see something very similar in this answer.

    or avoid this problem completely?

    You should only spin off operations on a background thread in case you don't care whether they succeed or fail. In most business transactions however, when the operation fails, you wish to either inform the user about this to let him know he has to retry, or you want to retry the operation automatically on his behalf. This is very hard to achieve reliably in case you just spin off operations on a background thread.

    A better solution would be to use a durable queuing mechanism. This can be a database queue or message queue. Such technology ensures that operations can't get lost and often have built-in retry mechanisms. Take a look at MassTransit or NServiceBus for instance that operateon top of RabbitMq, MSMQ or SQL Server.