介绍
开始很简单:假设我有一个基本控制器,它使用数据访问对象(在其中使用实体框架)来获取实体:
public class SomeController : Controller
{
private readonly DataAccess dataAccess;
public SomeController(DataAccess dataAccess)
{
this.dataAccess = dataAccess;
}
public ActionResult Index(int id)
{
var model = new Model();
model.Customer = this.dataAccess.Get(id);
return View(model);
}
}
问题
现在我想在我的数据访问类中执行一个异步任务,它可以运行大约5分钟,也许更长。我不想重用注入的数据访问类,因为我喜欢为每个HttpRequest保留一个实体框架上下文。所以我想这样做:
public class SomeController : Controller
{
private readonly DataAccess dataAccess;
private readonly DataAccess dataAccessForAsyncTask;
public SomeController(DataAccess dataAccess, DataAccess dataAccessForAsyncTask)
{
this.dataAccess = dataAccess;
this.dataAccessForAsyncTask = dataAccessForAsyncTask;
}
public ActionResult Index(int id)
{
var model = new Model();
model.Customer = this.dataAccess.Get(id);
this.dataAccessForAsyncTask.ExecuteAsyncTask();
return View(model);
}
}
问题2
我的数据访问类是使用.InstancePerHttpRequest()注册的,因为我喜欢为每个HttpRequest保留一个实体框架上下文。
问题
这甚至可能吗?或者我应该这样做完全不同? 如果可能,我如何使用Autofac完成此操作?
更新
根据Dennis Palmer的回答,我调整了我的代码。我的解决方案是创建一个必须执行的服务的新实例,这次不使用Autofac。 (因为必须执行异步的任务非常短,所以我选择在没有队列的情况下执行它。)
// This controller just calls a method on my Service Layer. Nothing special.
public class SomeController : Controller
{
private readonly Service service;
public SomeController(Service service)
{
this.service = service;
}
public ActionResult Index(int id)
{
var model = new Model();
model.Customer = this.service.Get(id);
return View(model);
}
}
// This is the Service where the magic happens.
Public class Service
{
private readonly DataAccess dataAccess;
// This constructor is used if we want to create a new
// instance without Autofac
public Service()
{
this.dataAccess = new DataAccess();
}
// This constructor is used if we want to let Autofac
// create a new instance
public Service(DataAccess dataAccess)
{
this.dataAccess = dataAccess;
}
public Customer Get(long id)
{
// Get the customer in a Synchronous way.
var customer = dataAccess.Get(id);
// Now we have to do something Async.
// Solution: create a new instance by hand, of the
// class that holds the method we want to call Async.
var serv = new Service();
// Execute the call in a async way.
new Action<long, long>(serv.DoSomething).BeginInvoke(null, null);
return customer;
}
// This is the method we want to execute async.
public void DoSomething()
{
// Do something short or long running.
}
}
答案 0 :(得分:3)
我认为您不需要单独的dataAccess
实例。即使您的请求立即返回,执行异步代码的线程仍需要保持活动状态,直到该代码完成执行。因此,每个请求的一个上下文应该可以正常工作。
View将立即返回给客户端,但服务该请求的线程应继续运行,直到异步任务完成。如果没有发生这种情况,那么你应该问一个关于如何让该线程保持足够长时间以使其发生的问题,并且了解更多关于线程和异步操作的人可以提供更好的答案。
编辑:(以回应评论)因此,如果数据上下文被处理掉,那么您就遇到了线程问题。如果实例化或注入数据上下文对象并不重要,如果线程的生存时间不足以使异步任务完成运行,那么它们就会被中断。
如果这是一个长时间运行的后台任务,我会考虑使用消息队列并实现一个后台任务,该任务在与MVC应用程序分开的独立进程上运行。类似于Windows Azure中的辅助角色。
答案 1 :(得分:0)
这都是关于终身范围的。使用InstancePerHttpRequest
时,Autofac创建的所有组件的生命周期范围都是单个Http请求。完成后,Autofac会处理所有组件。
解决方案非常简单:如果您的异步任务跨越多个Http请求,那么此任务将确定所需组件的生命周期范围。因此,您只需要开始一个新的生命周期范围,然后使用它来解析异步任务所需的所有组件,并在任务完成时配置范围。诀窍是:您需要从 root 范围创建生命周期范围,否则它将在HTTP请求结束时处理。这可以通过多种方式实现。
假设你使用MVC3并跟随the autofac integration instructions,你可以这样做:
public class App : HttpApplication
{
public ILifetimeScope RootLifetimeScope { get; private set; }
protected void Application_Start()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
// save the reference to be able to access it later
this.RootLifetimeScope = container;
//...
}
}
public class SomeController : Controller
{
private readonly Service service;
public SomeController(Service service)
{
this.service = service;
}
public ActionResult Index(int id)
{
var model = new Model();
model.Customer = this.service.Get(id);
return View(model);
}
}
Public class Service
{
private readonly DataAccess dataAccess;
readonly ILifetimeScope _OwnScope;
public Service(DataAccess dataAccess)
{
this.dataAccess = dataAccess;
}
public Customer Get(long id)
{
// Get the customer in a Synchronous way.
var customer = dataAccess.Get(id);
// Now we have to do something Async.
// Solution: create a separate lifetime scope that survives longer than HTTP request
Debug.Assert(HttpContext.Current != null)
var newScope = ((App)HttpContext.Current.ApplicationInstance)
.RootLifetimeScope.BeginLifetimeScope();
// DO NOT use using statement or you'll have your original troubles
try
{
var serv = newScope.Resolve<Service>();
// the serv instance now will have its own DataAccess
// which will be diposed only when newScope is disposed
// Execute the call in a async way.
new Action<long, long>(serv.DoSomething)
.BeginInvoke(ar =>
{
// finish the action if required
// DO NOT FORGET to dispose the scope, or you'll have a memory leak
newScope.Dispose();
}, null);
}
catch
{
// dispose the scope only if something goes wrong.
// if the code succeeds, you need to dipose the scope in the callback
newScope.Dispose();
throw;
}
return customer;
}
// This is the method we want to execute async.
public void DoSomething()
{
// Do something short or long running.
}
}
我认为应该有一种更优雅的方式,不使用服务定位器模式(即访问ApplicationInstance
),但只使用依赖注入。但我无法快速制作它。
<强>更新强>
另一种选择是使用owned instances。以下是重写原始代码的方法:
public class SomeController : Controller
{
private readonly DataAccess dataAccess;
private readonly Func<Owned<DataAccess>> dataAccessFactory;
public SomeController(DataAccess dataAccess, Func<Owned<DataAccess>> dataAccessFactory)
{
this.dataAccess = dataAccess;
this.dataAccessFactory = dataAccessFactory;
}
public ActionResult Index(int id)
{
var model = new Model();
model.Customer = this.dataAccess.Get(id);
Owned<DataAccess> dataAccessForAsyncTaskHolder = null;
try
{
dataAccessForAsyncTaskHolder = dataAccessFactory();
dataAccessForAsyncTaskHolder.Value.ExecuteAsyncTask(() =>
// you'll need a completion callback
{
// finish the task if required
// dipose the owned instance
dataAccessForAsyncTaskHolder.Dispose();
});
}
catch
{
if (dataAccessForAsyncTaskHolder != null)
dataAccessForAsyncTaskHolder.Dispose();
throw;
}
return View(model);
}
}