在 MVC3 应用程序中保留异步处理程序的依赖注入对象
我正在开发一个 MVC3 应用程序,我想在其中异步处理一些代码块。这些代码块需要执行一些数据库操作,并且需要访问我的 DAL 存储库,这些存储库是通过我的依赖项注入设置启动的。我已将存储库的生命周期设置为“每个 http 请求的实例”生命周期,并且我正在寻找一种方法来延长异步操作的生命周期。
public class AsyncController : Controller
{
private readonly ICommandBus commandBus;
private readonly IObjectRepository objectRepository;
public ActionResult DoThings()
{
DoThingsAsync();
return View();
}
private delegate void DoThingsDelegate();
private void DoThingsAsync()
{
var handler = new DoThingsDelegate(DoThingsNow);
handler.BeginInvoke(null, null);
}
private void DoThingsNow()
{
var objects = objectRepository.getAll();
foreach (Thing thing in objects)
{
thing.modifiedOn = DateTime.Now;
objectRepository.Update(thing);
}
}
}
objectRepository 在请求的生命周期内启动,我想仅为一个控制器中的一种方法跳过该对象的垃圾收集。我尝试将存储库作为参数传递给方法和委托,但这并没有延长其生命周期。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
当启动一个新线程时,明智的做法是让容器为您构建一个全新的对象图。当您重用在 HTTP 请求上下文(或线程上下文或其他上下文)中创建的依赖项时,这可能会导致竞争条件和其他类型的失败和奇怪的行为。
当您知道这些依赖项是线程安全的(或者使用每个请求的生命周期创建并且在触发异步操作后不被请求使用)时,当然不必是这种情况,但这不是一个简单的事情。这些依赖项的使用者应该知道或应该依赖,因为这是将所有内容连接在一起的应用程序部分的责任(组合根)。这样做也会使以后更改依赖项配置变得更加困难。
相反,您应该将
DoThings
方法重构为它自己的类,并让您的控制器依赖于某种 IDoThings
接口。通过这种方式,您可以推迟有关异步处理事物的决定,直到将所有内容连接在一起的那一刻。当在另一种类型的应用程序(例如 Windows 服务)中重用该逻辑时,它甚至允许您更改该操作异步执行的方式(或只是同步执行)。更进一步,实际的 DoThings 业务逻辑和异步执行该逻辑的部分是两个不同的关注点:两个独立的职责。 您应该将它们分成不同的类。
以下是我建议您执行的操作的示例:
定义一个接口:
让您的控制器依赖于该接口:
定义一个包含业务逻辑的实现:
定义一个知道如何异步处理
DoingThings
的代理:现在,您无需将
DoingThings
注入到SomeController
中,而是将DoingThingsAsync
注入到控制器中。控制器不知道该操作是否同步执行,也不在乎。除此之外,通过这种方式,您还可以将业务逻辑与表示逻辑分开,这很重要,有很多充分的理由。您可能需要考虑使用命令模式作为改变状态的业务操作的基础(如果您还没有使用这样的东西,请考虑使用 ICommandBus 接口)。例如,看一下这篇文章。使用此模式,您可以更轻松地将某些命令配置为异步运行或将它们批处理到外部事务队列,以供以后处理,而无需更改这些命令的任何使用者。
When starting a new thread, it is wise to let the Container compose a totally new object graph for you. When you reuse dependencies that are created in the context of a HTTP request (or thread context or what have you), this can result in race conditions and other sort of failures and strange behavior.
This of course doesn’t have to be the case when you know that those dependencies are thread-safe (or created with a per-request lifetime and are not used by the request after triggering the async operation), but this is not something a consumer of these dependencies should know or should depend on, because it is the responsibility of the part of the application that wires everything together (The Composition Root). Doing this, would also make it harder to change that dependency configuration later on.
Instead, you should refactor your
DoThings
method into its own class and let your Controller depend on some sort ofIDoThings
interface. This way you can postpone the decision about handling things asynchronously until the moment you wire everything together. When reusing that logic in another type of application (Windows Service for instance), it even allows you to change the way this operation is executed asynchronously (or simply execute it synchronously).To go even one step further, the actual
DoThings
business logic and the part that executes that logic asynchronously are two different concerns: two separate responsibilities. You should separate them into different classes.Here's an example of what I advise you to do:
Define an interface:
Let your Controller depend on that interface:
Define an implementation that contains the business logic:
Define a proxy that knows how to handle a
DoingThings
asynchronously:Now, instead of injecting a
DoingThings
into theSomeController
, you inject aDoingThingsAsync
into the controller. The controller does not know whether the operation is executed synchronously or not, and it doesn't care. Besides that, this way you are also separating your business logic from your presentation logic, which is important for lots of good reasons.You might want to consider using the command pattern as basis around business operations that mutate state (if you're not already using such a thing, considering the
ICommandBus
interface). Take for instance a look at this article. With this pattern you can more easily configure certain commands to run asynchronously or batch them to an external transactional queue, for later processing, without having to change any of the consumers of those commands.