Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac - resolving dependencies in multi thread environment

public class MultithreadTester
{

    public void Run()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ManualWork>().As<IWork>();
        builder.RegisterType<ColabManualWork>().As<IColabWork>();
        builder.RegisterType<RelaxAfterManualWork>().As<IRelax>();

        var container = builder.Build();

        //#1 - Simple single thread
        using (var scope = container.BeginLifetimeScope())
        {
            var work = scope.Resolve<IWork>();
            work.DoWork();
        }

        //#2 - Resolving dependecies in worker threads in scopes of these threads without passing lifetime scopes are container into implementation
        using (var scope = container.BeginLifetimeScope())
        {
            var work = scope.Resolve<IColabWork>();
            work.DoWork();
        }

        //#3 - Resolving dependecies in worker threads when original scope is already gone (simulates fast request on same service which spawns threads for request processing)
        IColabWork workForSample3;
        using (var scope = container.BeginLifetimeScope())
        {
            workForSample3 = scope.Resolve<IColabWork>();
        }
        workForSample3.DoWork();

        Console.ReadKey();

    }

    public interface IRelax
    {
        void DoRelax();
    }

    public class RelaxAfterManualWork : IRelax
    {
        public void DoRelax()
        {
            Console.WriteLine("Relaxing after hard work...");
            Thread.Sleep(1000);
            Console.WriteLine("Relax is done...");
        }
    }


    public interface IWork
    {
        void DoWork();
    }

    public class ManualWork : IWork
    {
        private readonly IRelax _relaxActivity;

        public ManualWork(IRelax relaxActivity)
        {
            _relaxActivity = relaxActivity;
        }

        public void DoWork()
        {
            Console.WriteLine("Ufff, this is so hard...");
            Thread.Sleep(5000);
            Console.WriteLine("Work is done...");
            _relaxActivity.DoRelax();
        }
    }

    public interface IColabWork
    {
        void DoWork();
    }

    public class ColabManualWork : IColabWork
    {
        public void DoWork()
        {
            Console.WriteLine("We must discuss how to share the workload...");
            Thread.Sleep(1500);

            Action action = () => 
            {
                //IT WOULD BE FINE TO HAVE RESOLVED DEPENDENCIES PER THREAD AND IN THREAD OWN LIFETIMESCOPE

                Console.WriteLine("Ufff, this is so hard but working with my buddies helps...");
                Thread.Sleep(2500);
                Console.WriteLine("Work is done...");
                var relaxActivity = new RelaxAfterManualWork();
                relaxActivity.DoRelax();
            };

            var thread1 = new Thread(() => { action(); });
            var thread2 = new Thread(() => { action(); });
            thread1.Start();
            thread2.Start();

            thread1.Join();
            thread2.Join();
        }
    }


}

In sample marked as #1 I am resolving IWork and run some action. For single thread environment I understand what is going on in DI, how I should work with DI, lifetimescope and how to resolve dependencies.

But I have trouble to understand DI in multi thread environment. I try to demonstrate some issues I have is samples #2, #3. In these samples I would somehow need to solve dependencies in LifetimeScope which would be created for each threads in ColabManualWork. Of course I do not want references on any class from Autofac to prevent coupling.

I even created simple factory which would be suitable for creating nested LifetimeScopes from current one:

public interface IIsolatedLifetimeScopeFactory<TA>
{
    void Create(Action<TA> action);
}

public class IsolatedLifetimeScopeFactory<TA> : IIsolatedLifetimeScopeFactory<TA>
{
    private readonly ILifetimeScope _scope;

    public IsolatedLifetimeScopeFactory(ILifetimeScope scope)
    {
        _scope = scope;
    }

    public void Create(Action<TA> action)
    {
        using (var subScope = _scope.BeginLifetimeScope())
        {
            var a = subScope.Resolve<TA>();
            action(a);
        }
    }
}

But I do not like this solution well. There are three big issues - 1) All logic must be in lambda function (or equivalent method); 2) in future Autoflac can re-implement functionality of disposing child scopes if parent scope is disposed again (this functionality was already here for few months); 3) As demonstrated in sample #3 I can dispose parent LifetimeScope before any functionality in ColabManualWork is even started and thus my factory would be using already disposed LifetimeScope.

Can somebody help me how to effectively solve resolving issues in worker threads? I read something related to SimpleInjector named Work with dependency injection in multi-threaded applications but I do not fully get it plus it is not Autofac related. In that article is written In a multi-threaded application, each thread should get its own object graph. This means that you should typically call container.GetInstance() once at the beginning of the thread’s execution to get the root object for processing that thread

How to solve dependencies in worker threads without coupling with Autofac and in thread-related lifetimescope?

like image 265
user2126375 Avatar asked Oct 20 '22 06:10

user2126375


1 Answers

To give each thread its own lifetime scope, you just need to register your IsolatedLifetimeScopeFactory as SingleInstance. This will solve your concerns 2) and 3)

[TestMethod]
public void MyTestMethod()
{
    var cb = new ContainerBuilder();
    cb.RegisterGeneric(typeof(IsolatedLifetimeScopeFactory<>))
        .SingleInstance();
    var container = cb.Build();

    using (var scope1 = container.BeginLifetimeScope("scope1"))
    using (var scope2 = scope1.BeginLifetimeScope("scope2"))
    {
        var factory = scope2.Resolve<IsolatedLifetimeScopeFactory<object>>();
        var tag = factory._scope.Tag; // made _scope public for testing purposes
        Assert.AreNotEqual("scope1", tag);
        Assert.AreNotEqual("scope2", tag);

        // This particular string "root" is probably not guaranteed behavior, but
        // being in the root scope is guaranteed for SingleInstance registrations.
        Assert.AreEqual("root", tag);
    }
}

Your concern 1) could be solved by using a different abstraction. For example, you could add this to the IsolatedLifetimeScopeFactory

public Autofac.Features.OwnedInstances.Owned<TA> Create()
{
    return _scope.Resolve<Autofac.Features.OwnedInstances.Owned<TA>>();
}

And you could hide Owned behind an abstraction if you really wanted to, although I would say that's overkill.

like image 162
default.kramer Avatar answered Oct 29 '22 22:10

default.kramer