2

I have the following WCF service:

public class TimeService : ITimeService
{
    private FooFactory _fooFactory;
    public TimeService(FooFactory fooFactory)
    {
        _fooFactory = fooFactory;
    }

    public DateTime Now()
    {
        Foo result = null;
        Stopwatch sw = new Stopwatch();
        sw.Start();
        Debug.Print($"[{Thread.CurrentThread.ManagedThreadId}] BEGIN Operation");
        Task.Run<Foo>(() =>
        {
            Debug.Print($"[{Thread.CurrentThread.ManagedThreadId}] BEGIN Task {sw.ElapsedMilliseconds}ms");
            Task.Delay(TimeSpan.FromSeconds(10)).Wait();
            return _fooFactory.Create();
        }).ContinueWith(x =>
        {
            try
            {
                Debug.Print($"[{Thread.CurrentThread.ManagedThreadId}] CONTINUE Task {sw.ElapsedMilliseconds}ms");
                result = x.Result;
                Debug.Print($"[{Thread.CurrentThread.ManagedThreadId}] CONTINUE Task {sw.ElapsedMilliseconds}ms, Result = {result}");
            }
            catch (Exception ex)
            {
                Debug.Print($"[{Thread.CurrentThread.ManagedThreadId}] CONTINUE Task {ex}");
            }
        });
        Debug.Print($"[{Thread.CurrentThread.ManagedThreadId}] END Operation {sw.ElapsedMilliseconds}ms");
        return result?.Now ?? DateTime.MinValue;
    }
}

And this:

public class FooFactory
{
    private IComponentContext _componentContext;
    public FooFactory(Owned<IComponentContext> componentContext)
    {
        _componentContext = componentContext.Value;
    }
    public Foo Create()
    {
        return _componentContext.Resolve<Foo>();
    }
}

The registration looks like this:

AutofacHostFactory.Container = CreateContainer();
...
private IContainer CreateContainer()
{
    ContainerBuilder builder = new ContainerBuilder();
    builder.RegisterType<Foo>().AsSelf();
    builder.RegisterType<FooFactory>().AsSelf().InstancePerLifetimeScope();
    //builder.Register(c => new FooFactory(c.Resolve<IComponentContext>()));
    builder.RegisterType<TimeService>().As<ITimeService>().AsSelf();
    return builder.Build();
}

Basically, I have an operation that returns to the caller right away, while the task I started continues in the background. Surprisingly, this did not terminate the task when the request "completed" - I would have thought that it would.

FooFactory needs to create Foo dynamically and that's why it takes a dependency on IComponentContext(or ILifetimeScope - is there a difference?). When I take a dependency on IComponentContext directly or by using a delegate as in the commented out code, an exception occurs saying the IComponentContext instance is disposed. I'm guessing this is because the parent ILifetimeScope which was associated with the request (via Autofac.WCF) is disposed. But if I take a dependency on Owned instead, the exception does not occur. So it seems the code is working and doing what I want.

However, the question is, is this safe?

I mean, the request completes and response is returned to the caller first and the instance resolution occurs afterwards from the context that was created for the request. As far as Autofac is concerned, the scope for the request is gone. Imagine we were deep in the nested scope when FooFactory call came.

What are the implications?

Jiho Han
  • 1,610
  • 1
  • 19
  • 41

1 Answers1

1

There are many implicit questions in this question, and a simple response is not possible. So I'll try to explain some of the doubts expressed.

First: what's the difference between IComponentContext and ILifetimeScope? As for this question,

Both interfaces provide the standard Resolve() operations, while ILifetimeScope extends IComponentContext by adding methods for starting new nested lifetimes.

So, given there shouldn't be any performance penalties, I would always use ILifetimeScope, because you can open nested scopes if needed.

To properly instantiate components you need a valid scope (or a context, I'll stick with "scope" for the rest of the answer). The nice thing about scopes is that when Autofac will dispose them, it will automatically dispose every component resolved by them.

The scope in which the FooFactory is resolved is the WCF request scope, which is automagically disposed when the request completes.

So the exception you where getting is expected: the context has been disposed and can no longer be used.

Owned<> exists for exactly this purpose: to provide objects with an autonomous scope, so they are NOT disposed when the parent scope closes.

It is safe? YES, provided you call dispose when finished, because Autofac is not going to know when the disposal is needed.

As a reference (possibly for other readers) the excellent Autofac Lifetime Primer covers everything you need to know about the use of this tools.

Let me add some points about possible shortcomings of your implementation:

  • you are not calling Dispose() on FooFactory's owned componentContext. So you are leaking a context for each request, probably. And, if Foo should be disposed, no one is going to do it.
  • Second: I noticed that you registered the FooFactory as "InstancePerLifetime". Why? As I see it, a Factory could and should be instantiated only once. I would change it in "SingleInstance".
  • Third: are you sure you need the FooFactory at all? Autofac already provides a simple mechanism for lazy initialization, which requires taking a dependency from Func<componentType> (in you example Func) or an Owned<<Func<Foo>>. It really boils down about readability, so this is debatable. Instead of requireing FooFactory you require a delegate Func<Foo> and call it every time you need a Foo. In fact, FooFactory could require a Func<Foo> and the Create method would just call the delegate.
  • Fourth, and last. In order to clean up the task invocation (and ensure Dispose is called if and when needed) I would change it considering to bind the task to an Owned, in this way you create a proper Autofac scope in which components can be resolved.

Just require an Owned<ILifetimeScope> instead of a FooFactory and Dispose it when the task is finished. If you need further advice, let me know.

Good luck!

Community
  • 1
  • 1
Alberto Chiesa
  • 7,022
  • 2
  • 26
  • 53