我有一个导入操作,我想在另一个线程上执行,希望UI立即响应。所以,我开始沿着这条道路创建了一个这样的动作:
[HttpPost]
public async Task<RedirectToRouteResult> ImportAsync(HttpPostedFileBase file)
{
var importsRoot = Server.MapPath("~/App_Data/Imports");
var path = Path.ChangeExtension(Path.Combine(importsRoot, Guid.NewGuid().ToString()), "txt");
if (!Directory.Exists(importsRoot))
{
Directory.CreateDirectory(importsRoot);
}
file.SaveAs(path);
// start the import process
await ImportManager.Instance.StartImport(User.Identity.Name);
return RedirectToAction("Index");
}
ImportManager
现在有两个目的:
StartImport
方法如下所示:
public async Task StartImport(string user)
{
string[] files;
var importRoot = HttpContext.Current.Server.MapPath("~/App_Data/Imports");
var processingRoot = HttpContext.Current.Server.MapPath("~/App_Data/Processing");
var processedRoot = HttpContext.Current.Server.MapPath("~/App_Data/Processed");
lock (lockObj)
{
// make sure the "Processing" folder exists
if (!Directory.Exists(processingRoot))
{
Directory.CreateDirectory(processingRoot);
}
// find all of the files available and move them to the "Processing" folder
files = Directory.GetFiles(importRoot);
foreach (var file in files)
{
var fileName = Path.GetFileName(file);
if (fileName == null)
{
continue;
}
File.Move(file, Path.Combine(processingRoot, fileName));
}
// make sure the "Processed" directory exists
if (!Directory.Exists(processedRoot))
{
Directory.CreateDirectory(processedRoot);
}
}
await Task.Run(() =>
{
// start processing the files
foreach (var file in files)
{
var fileName = Path.GetFileName(file);
if (fileName == null)
{
continue;
}
var processingFileName = Path.Combine(processingRoot, fileName);
var processedFileName = Path.Combine(processedRoot, fileName);
var recognizer = new Recognizer(processingFileName);
recognizer.ProgressChanged += (s, e) => Clients.All.updateImportStatus(e.ProgressPercentage, user);
recognizer.Recognize(DataManager.GetExclusionPatterns());
// move the file to the "Processed" folder
File.Move(processingFileName, processedFileName);
}
Clients.All.importComplete();
});
}
调试我们发现,当我点击await Task.Run(() =>
它同步运行时(无论如何),因为UI没有得到重定向到Index
的请求,直到读完30K +行。
我怎样才能让它简单地执行并忘记它?我需要使用不同的方法吗?
答案 0 :(得分:13)
它以异步方式运行;但你在等待它:
await ImportManager.Instance.StartImport(User.Identity.Name);
return RedirectToAction("Index");
不等待;等待。 RedirectToAction
现在是一个继续,将在其他代码完成时调用。
如果你不想等待;不要await
。但是,你应该考虑一下如果发生错误会发生什么等等。如果没有人去观察它,不要让你的async
方法引发异常:坏事。
答案 1 :(得分:6)
我怎样才能让它简单地执行并忘记它?
我强烈建议您不要使用&#34;开火并忘记&#34;在ASP.NET上。核心原因是ASP.NET在请求生命周期内管理您的应用程序生命周期。因此,如果您执行的代码不是请求的一部分,那么ASP.NET可能会删除您的应用程序。在一般情况下,这意味着无法依赖实际执行的代码。
我需要使用其他方法吗?
正确的解决方案是将工作放入可靠的队列(例如,Azure队列或MSMQ),并使独立的后端执行实际处理(例如,Azure webrole,Azure辅助角色或Win32服务)。这是相当多的工作,但它是唯一的可靠的解决方案。
但是,如果您希望危险地生活并将工作保留在ASP.NET进程的内存中,则应该使用ASP.NET运行时注册该工作。您仍然没有保证处理将执行,但它可能可能,因为ASP.NET知道您的代码。我有NuGet package that will do that for you; just use BackgroundTaskManager.Run
instead of Task.Run
。