使用简单注入器时在哪里放置AsyncScopedLifestyle

时间:2019-06-03 16:09:07

标签: c# dependency-injection async-await ioc-container simple-injector

我正在编写一个在呼叫中心使用的应用程序。每当电话到达工作站时,我都需要创建一组对象(大约30个)。我希望这些对象仅在电话通话期间存在,因为它们包含状态,并且我认为创建新对象比每次通话时尝试重置其状态更有意义。创建这些对象时,它必须执行一些异步活动,例如为其他应用程序建立多个套接字并将消息发送给它们。电话结束后,它必须执行更多异步操作,例如发送结束消息,然后关闭套接字。

我一直在研究Simple Injector的AsyncScopedLifestyle功能。这是我认为我将如何使用它的简化示例:

class CallTaskFactory
{
    private readonly Container Container;

    public CallTaskFactory(Container container)
    {
        Container = container;
    }

    public async Task CreateCallTask()
    {
        using (Scope scope = AsyncScopedLifestyle.BeginScope(Container))
        {
            // Get the socket's destination
            SocketDestinationProvider socketDestProvider =
                Container.GetInstance<SocketDestinationProvider>();

            EndPoint ep = await socketDestProvider.GetSocketDestination();

            // Now create a socket and connect to that destination
            Socket socket = Container.GetInstance<Socket>();
            await socket.ConnectAsync(ep);

            // Send a simple message on the socket
            var Sender1 = Container.GetInstance<MessageSender1>();
            await Sender1.SendStartMessage();

            // Send another message, and the response tells us whether we need
            // to create some object that does something on a timer
            var Sender2 = Container.GetInstance<MessageSender2>();
            var Response = await Sender2.SendStartMessageAndAwaitResponse();
            if (Response.Result)
            {
                Container.GetInstance<ClassThatChecksSomethingOnATimer>();
            }

            // The call stays active until the socket closes
            TaskCompletionSource<int> Completion = new TaskCompletionSource<int>();
            socket.Closed += (sender, e) => { Completion.TrySetResult(0); };
            await Completion.Task;

            // Clean up
            await Sender2.SendStopMessage();
            await Sender1.SendStopMessage();
            await socket.DisconnectAsync();
        }
    }
}

不过,我不确定是否将其放置在正确的位置。我认为此工厂类必须存在于我的“合成根”中,因为它引用了特定的DI容器。但是对我来说,“合成根”仅用于组成对象图,通常它不像上面的代码那样使用这些对象的逻辑。

如何在一个地方创建一组对象并在另一个地方使用它们,然后在完成工作后销毁它们?

1 个答案:

答案 0 :(得分:0)

  

我认为这个工厂类必须存在于我的合成根目录中,因为它引用了特定的DI容器。

绝对。只有Composition Root应该引用该容器。这通常意味着您需要引入一个抽象。这样,您就可以实现依赖于“合成根”内部DI容器的适配器逻辑,而应用程序代码可以依赖于其抽象。

  

但是对我而言,合成根仅用于组成对象图,并且通常不像上面的代码那样使用这些对象的逻辑。

您应避免将业务逻辑放在“合成根”中。合成根是应用程序基础结构的一部分。但是,这并不意味着它只能 组成对象图。允许调用创建的对象图。如果不允许对组合对象图进行操作,则将很难做任何有用的事情。

  

如何在一个地方创建一组对象并在另一个地方使用它们,然后在完成工作后销毁它们?

要解决此问题,您应该尝试从另一个角度解决这个问题。理想情况下,您的“合成根”应该仅对GetInstance进行一次调用,而对已解析的对象进行一次方法调用。通常可以通过将所有代码包装在一个新类中来实现。解决的依赖关系将在新类中提升为构造函数参数。

将这种技术应用于代码时,最终会得到如下结果:

public class CallExecutor {
    ...        
    public CallExecutor(
        SocketDestinationProvider socketDestinationProvider, Socket socket,
        MessageSender1 messageSender1, MessageSender2 messageSender2,
        ClassThatChecksSomethingOnATimer checker)
    {
        this.socketDestinationProvider = socketDestinationProvider;
        this.socket = socket;
        this.messageSender1 = messageSender1;
        this.messageSender2 = messageSender2;
        this.checker = checker;
    }

    public async Task Call()
    {
        EndPoint ep = await this.socketDestProvider.GetSocketDestination();
        await this.socket.ConnectAsync(ep);
        await this.sender1.SendStartMessage();
        var response = await this.sSender2.SendStartMessageAndAwaitResponse();
        if (response.Result)
        {
            this.checker.DoSomething(...)
        }

        TaskCompletionSource<int> Completion = new TaskCompletionSource<int>();
        socket.Closed += (sender, e) => { Completion.TrySetResult(0); };
        await Completion.Task;

        await this.sender2.SendStopMessage();
        await this.sender1.SendStopMessage();
        await this.socket.DisconnectAsync();        
    }
}

在上面的代码中,我做了一个简单的一对一转换。每个解决方案都成为构造函数依赖项。这可能并非在所有情况下都是正确的。我可以想象,例如,您想从容器中解析多个Socket实例。但是,请注意,Socket在我看来更像是运行时数据。您可能要考虑避免使用容器来解析此类对象(在this article中已提及)。

这个新的CallExecutor类可以完全定义为应用程序代码;不依赖于DI容器。 CallTaskFactory的其余代码非常短,以至于可以在您的“合成根目录”中轻松实现:

class CallTaskFactory : ICallTaskFactory
{
    private Container container;

    public CallTaskFactory(Container container)
    {
        this.container = container;
    }

    public async Task CreateCallTask()
    {
        using (AsyncScopedLifestyle.BeginScope(this.container))
        {
            await this.container.GetInstance<CallExecutor>().Call();
        }
    }
}

当然,CallExecutor的引入确实导致了所有依赖关系的创建。这似乎效率低下,但事实并非如此,因为object composition should be fast,所以您可以compose your graphs with confidence。与Simple Injector结合使用时,您几乎不会遇到性能问题,因为Simple Injector可以轻松地在一瞬间创建数千个对象。