(问题已解决)我有一个 MVC 应用程序,在我的操作中:
第一种情况:任务从未启动。
public ActionResult Insert(NewsManagementModel model) {
//Do some stuff
//Insert history
//new object NewsHistoryDto as the parameter
Task.Factory.StartNew(() => InsertNewsHistory(new NewsHistoryDto {
UserId = 1234,
Time = DateTime.Now,
Id = 1234
}));
return RedirectToAction("Index", "NewsManagement");
}
第二种情况:任务正常运行
public ActionResult Insert(NewsManagementModel model) {
//Do some stuff
//Insert history
//object NewsHistoryDto was declared outside
var history = new NewsHistoryDto {
UserId = 1234,
Time = DateTime.Now,
Id = 1234
};
Task.Factory.StartNew(() => InsertNewsHistory(history));
return RedirectToAction("Index", "NewsManagement");
}
我的问题是:当Task.Factory.StartNew和我在其中放置一个方法时,该方法(对象)的参数必须在外部声明???因为当我像第一种情况一样写得很快时,我将"new"关键字放在参数中,任务永远不会运行。原因:在行动中,我想尽快返回视图,任何其他与该视图无关的东西都将在任务中执行,客户端无需等待完成。
我很抱歉我的英语:)不好
更新 1:谢谢Panagiotis Kanavos,我使用了QueueBackgroundWorkItem,但问题仍然是一样的,如果我在外面声明对象,这个方法可以正常运行。但是当我在参数中使用 new 关键字时,此方法永远不会运行。没有例外,没有错误。谁能向我解释一下这怎么可能:(
更新 2:我尝试两种情况:
第一:
HostingEnvironment.QueueBackgroundWorkItem(delegate {
var handler = m_bussinessHandler;
handler.InsertNewsHistoryAsync(new NewsHistoryDto {
UserId = UserModel.Current.UserId,
Time = DateTime.Now,
Id = newsId
});
});-> still doens't works
第二:
var history = new NewsHistoryDto {
UserId = UserModel.Current.UserId,
Time = DateTime.Now,
Id = newsId
};
HostingEnvironment.QueueBackgroundWorkItem(delegate {
var handler = m_bussinessHandler;
handler.InsertNewsHistoryAsync(history);
});-> works normally
那么问题出在哪里呢???这与m_bussinessHandler无关,因为我复制了。
更新3:我找到了原因。原因是UserModel.Current,这是一个HttpContext.Current.Session["UserModel"]
中的对象,在这种情况下,当我调用async方法时,当此方法实际执行时,它可以访问HttpContext.Current,这是空的。所以我可以通过在外部声明对象来存储数据并将其传递给方法来解决这个问题,或者我捕获UserModel.Current并将其传递给此方法以使用UserModel.Current.UserId。
我的问题实际上解决了,谢谢大家对我的帮助,尤其是Panagiotis Kanavos。
实际上,您的代码会在任务完成之前返回给调用方。在代码返回时,任务甚至可能尚未开始执行,尤其是在调试方法时。调试器冻结所有线程,并逐步仅执行其中一个线程。
此外。NET 的引用捕获意味着当您使用 m_businessHandler
时,您捕获的是对m_businessHandler
字段的引用,而不是其值。如果您的控制器被垃圾回收,这将导致 NullReferenceException。为避免这种情况,您必须在 lambda 中复制字段的值。
您应该编写一个适当的异步方法,该方法仅在异步操作完成时返回给用户:
public async Task<ActionResult> Insert(NewsManagementModel model) {
//Do some stuff
//Insert history
//new object NewsHistoryDto as the parameter
await Task.Run(() =>
var handler=m_bussinessHandler;
handler.InsertNewsHistory(new NewsHistoryDto {
UserId = 1234,
Time = DateTime.Now,
Id = 1234
}));
return RedirectToAction("Index", "NewsManagement");
}
Task.Run
或Task.Factory.StartNew
大致相同。
即便如此,使用 Task.Run
伪造异步执行也不是一个好主意 - 您只需将执行从一个服务器线程切换到另一个服务器线程。您应该一直使用异步方法到数据库,例如。如果您使用的是 ADO.NET,请使用 ExecuteNonQueryAsync,或者在实体框架中使用 SaveChangesAsync。这样,在代码等待数据库调用完成时,不会执行或阻塞任何线程。
编译器还负责捕获字段的值,因此您无需复制任何内容。生成的代码要简洁得多:
public async Task<ActionResult> Insert(NewsManagementModel model) {
//Do some stuff
//Insert history
//new object NewsHistoryDto as the parameter
await m_bussinessHandler.InsertNewsHistoryAsync(new NewsHistoryDto {
UserId = 1234,
Time = DateTime.Now,
Id = 1234
};
return RedirectToAction("Index", "NewsManagement");
}
如果确实希望操作在后台运行并立即返回到客户端,则可以使用 QueueBackgroundWorkItem 启动新任务并将其注册到 IIS。在这种情况下,您必须再次复制字段的值:
public ActionResult Insert(NewsManagementModel model) {
//Do some stuff
//Insert history
//new object NewsHistoryDto as the parameter
HostingEnvironment.QueueBackgroundWorkItem(ct=>
var handler=m_bussinessHandler;
handler.InsertNewsHistoryAsync(new NewsHistoryDto {
UserId = 1234,
Time = DateTime.Now,
Id = 1234
});
return RedirectToAction("Index", "NewsManagement");
}
IIS 仍然可以取消任务,例如,当应用程序池回收时。在这样做之前,它将通过传递给 lambda(ct
参数)的 CancelTask 通知任务。如果任务没有及时完成,IIS 将继续并中止线程。长时间运行的任务应定期检查令牌。
您可以将取消令牌传递给大多数异步方法,例如 ExecuteNonQueryAsync(CancelToken)。这将导致 IO 操作在安全的情况下立即取消,而不是等到远程服务器响应。
Scott Hanselman 有一篇很好的文章,描述了如何在后台运行后台任务的所有可用方法 ASP.NET
您的m_bussinessHandler
是实例字段吗?因为它可以在您完成操作后被处理掉。