10

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 (Heading: Prevents multi-threading): Link 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.

Kieran Devlin
  • 1,373
  • 1
  • 12
  • 28
  • 2
    Related: https://stackoverflow.com/questions/45128033/what-is-the-correct-place-to-add-a-database-driven-scheduler-in-asp-net-core – Steven Jul 18 '17 at 09:38
  • 2
    My two cents: I'm not sure if your WebApp is the right place to be doing grunt processing - or if it some task that requires waiting maybe still not the right place. My suggestion would be to create a seperate program for this with an endpoint that your webApp can call. If you are interested in this solution let me know and I'll create an example. – Terrance00 Jul 18 '17 at 10:20
  • In my case I have a document generator and I've combined it with an API interface. The alternative would be to have the API separately and queue jobs to another application service but seeing as this really only had one function it didn't make much sense. If the API grows in functionality I will probably refactor and take this approach. – Kieran Devlin Jul 19 '17 at 13:46

1 Answers1

9

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.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    We have a notification system, so if the task fails, the user is notified of the result and they can proceed as they see fit. We use a queue mechanism but for this case its not applicable. I have attempted to create a new scope and getting the services within that scope and this seems to work fine except for when getting the database context. This seems to still throw a disposed exception even when instanced within the new scope. I have tried the following: `contextFactory.Create(new DbContextFactoryOptions());` And `scope.ServiceProvider.GetService();` – Kieran Devlin Jul 18 '17 at 11:08
  • After looking at the stack overflow example you posted I managed to get this to work under my database context. Thanks you've helped me greatly! – Kieran Devlin Jul 18 '17 at 12:15
  • 3
    "if the task dails, the user is notified". But what if the web service gets recycled, crashes or the server crashes in the middle of this background operation. This will cause the user to get unnotified. Durable queing is _the_ solution to this problem. – Steven Jul 19 '17 at 06:39