Task.Run没有像我想象的那样异步运行?

Mik*_*oud 5 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");
}
Run Code Online (Sandbox Code Playgroud)

ImportManager现在有两个目的:

  1. 处理导入.
  2. 通过SignalR通知所有客户输入状态.

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();
    });
}
Run Code Online (Sandbox Code Playgroud)

发生了什么?

调试我们发现,当我点击await Task.Run(() =>它同步运行(一段时间),因为UI没有得到重定向的请求,Index直到说已读取30K +行.

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

Mar*_*ell 13

它以异步方式运行; 但是你在等待它:

await ImportManager.Instance.StartImport(User.Identity.Name);

return RedirectToAction("Index");
Run Code Online (Sandbox Code Playgroud)

不等 等待.现在RedirectToAction是一个继续,将在其他代码完成时调用.

如果你不想等待; 不要await.但是,您应该考虑如果发生错误等会发生什么.async如果没有人要观察它,请不要让您的方法引发异常:坏事.


Ste*_*ary 6

我怎样才能让它简单地执行并忘记它?

我强烈建议你不要在ASP.NET上使用"fire and forget".核心原因是ASP.NET在请求生命周期内管理您的应用程序生命周期.因此,如果您执行的代码不是请求的一部分,那么ASP.NET可能会删除您的应用程序.在一般情况下,这意味着您不能依赖于实际执行的代码.

我需要使用不同的方法吗?

正确的解决方案是将工作放入可靠的队列(例如,Azure队列或MSMQ),并使独立的后端执行实际处理(例如,Azure webrole,Azure辅助角色或Win32服务).这是相当多的工作,但它是唯一可靠的解决方案.

但是,如果您希望危险地生活并将工作保留在ASP.NET进程的内存中,则应该使用ASP.NET运行时注册该工作.您仍然无法保证处理将执行,但更有可能,因为ASP.NET知道您的代码.我有一个NuGet包,可以帮到你; 只是用BackgroundTaskManager.Run而不是Task.Run.