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

Where to place AsyncScopedLifestyle when using Simple Injector

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

我一直在研究 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();
        }
    }
}

不过,我不确定我是否把它放在了正确的位置。我假设这个工厂 class 必须存在于我的 Composition Root 中,因为它引用了一个特定的 DI 容器。但对我来说,Composition Root 仅用于组合对象图,通常它没有使用这些对象的逻辑,如上面的代码那样。

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

I assume this factory class would have to exist inside my composition root, because it references a specific DI container.

当然可以。只有 Composition Root 应该引用容器。这通常意味着您需要引入抽象。这允许您实现依赖于 DI 容器 inside 的适配器逻辑,而应用程序代码可以依赖于它的抽象。

But to me the composition root is for composing an object graph only, and generally it doesn't have logic that uses those objects, as the above code does.

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

How can I create a set of objects in one place and consume them in another, and then destroy them when their work is done?

要解决这个问题,您应该尝试从不同的角度来解决这个问题。理想情况下,您的 Composition Root 应该只对 GetInstance 进行一次调用,并对已解析的对象进行一次方法调用。这通常可以通过将所有代码包装在一个新的 class 中来实现。已解析的依赖项将被提升为新 class.

中的构造函数参数

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

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();        
    }
}

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

这个新的CallExecutorclass可以完全定义为应用程序代码;不依赖于 DI 容器。 CallTaskFactory 的剩余代码非常短,可以很容易地在您的 Composition Root 中实现:

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, so you can compose your graphs with confidence。当与 Simple Injector 结合使用时,您几乎不会遇到性能问题,因为 Simple Injector 可以轻松地在瞬间创建数千个对象。