我正在编写一个在呼叫中心使用的应用程序。每当电话到达工作站时,我都需要创建一组对象(大约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容器。但是对我来说,“合成根”仅用于组成对象图,通常它不像上面的代码那样使用这些对象的逻辑。
如何在一个地方创建一组对象并在另一个地方使用它们,然后在完成工作后销毁它们?
答案 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可以轻松地在一瞬间创建数千个对象。