Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using dependencies on multiple threads with Parallel.ForEach

I use Simple Injector as my IoC container. SimpleInjector uses this simple technique to handle mixed life style for Per Thread and Per Web Request

container.RegisterPerWebRequest<IWebRepository, Repository>();
container.RegisterLifetimeScope<IThreadRepository, Repository>();
container.Register<IRepository>(container.GetInstance<Repository>());

// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<Repository>(() =>
{
    Repository repository;
    if (HttpContext.Current != null)
        repository = (Repository)container.GetInstance<IWebRepository>();
    else
        repository = (Repository)container.GetInstance<IThreadRepository>();

    return repository;
});

Unfortunately (and obviously!), elsewhere in my UnitOfWork class this is giving me an issue when I use Parallel.ForEach and try to call into multiple instances of the Repository class in parallel as only the first of the threads finds a value in HttpContext.Current

using (TransactionScope scope = new TransactionScope())
{
    Parallel.ForEach(new List<IRepository>() { _repository1, _repository2 ... }, 
        (repository) =>
        {
            repository.Commit();
        });
    scope.Complete();
}

Now that I've finished writing out the question I can see I'm probably asking for the impossible or something stupid ... but what the hell ... can this be done? Can a single request/thread registration be made available to multiple internal threads?

like image 709
qujck Avatar asked Dec 21 '12 00:12

qujck


1 Answers

With dependency injection, you try to centralize the knowledge about the lifetime of objects. This centralized place is called the Composition Root. When you start passing dependencies from one thread to the other, those parts of the code have to know whether it is safe to pass those dependencies. For instance, are those dependencies thread safe? Can those dependencies run in that new context (outside an HTTP or WCF request for instance)? This might be trivial to analyze in many situations, but prevents you to change those dependencies with other implementations, since now you have to remember that there is a place in your code where this is happening and you need to know which dependencies are passed on. You are decentralizing this knowledge again, making it harder to reason about the correctness of your DI configuration and making it easier to misconfigure the container in a way that makes your code fail directly (at best) or causes hard-to-debug race conditions (at worst).

It is, therefore, safest to let each newly started threads build a new object graph by asking the container for it. Don't pass on dependencies (that means: instances that are managed and injected by the container) from one thread to the other.

In your case you even seem to hit problems as your repositories seem to have a relationship with the HTTP context because they not seem to be transmissible to other threads. In that case it is pretty 'easy': don’t do this ;-)

This doesn't mean that you can't do multi-threading to speed up performance in any way. But you have to make sure that this operation does not move dependencies from one thread to thread, or when they do; make sure that this code (in your case the Parallel.ForEach) is located inside the Composition Root of your application, because this is the only place that can/should know whether this operation is safe to execute.

In your specific case, however, I find it quite scary that you are committing over multiple threads. Shouldn't the commit of that unit of work be atomic? Are you sure that this commit is still atomic when you execute the repository commits on different threads? What happens when one of the commits fails but others succeed? How do you rollback the already succeeded operations?

I think you can (and you might already have) solved these problems, but such solution adds a lot of complexity to the system. You have to ask yourself whether the performance improvement compensates with the added complexity.

You can find more info about working with dependency injection in multi-threaded applications here.

like image 112
Steven Avatar answered Nov 15 '22 12:11

Steven