Task.Run不像我想象的那样异步运行吗?

时间:2014-04-17 15:43:57

标签: c# asynchronous async-await asp.net-mvc-5

我有一个导入操作,我想在另一个线程上执行,希望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现在有两个目的:

  1. 处理导入。
  2. 通过SignalR向所有客户通知导入状态。
  3. 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 +行。

    我怎样才能让它简单地执行并忘记它?我需要使用不同的方法吗?

2 个答案:

答案 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