【问题标题】:MVC4 + async/await + return response before action completesMVC4 + async/await + 在操作完成之前返回响应
【发布时间】:2025-12-17 08:15:01
【问题描述】:

在我的 MVC4 应用程序中,我需要添加一个控制器来上传和处理大文件。文件上传后,我需要立即开始对该文件进行异步处理并将响应返回给浏览器,而无需等待处理完成。

显然我可以启动一个新线程来手动处理文件,但我想知道我是否可以使用 .net 4.5 引入的 async/await 机制来实现这个场景

为了测试这个概念,我尝试了这样的方法:

public async Task<ActionResult> Test()
{
    TestAsync();
    return View("Test");
}

public async void TestAsync()
{
    await LongRunning();
}

private Task<int> LongRunning()
{
    return Task<int>.Factory.StartNew(() => Pause());
}

private int Pause()
{
    Thread.Sleep(10000);
    return 3;
}

异步机制似乎在一般情况下工作:当我调试代码时,我点击了“return View("Test");” “return 3”行之前的行。但是,浏览器仅在 Pause 方法完成后才会收到响应。

这似乎表现得像常规异步控制器(具有 Async 和 Completed 方法的控制器)。有没有办法在我的场景中使用控制器中的 async/await?

【问题讨论】:

  • 我不确定直接在 ASP.NET 应用程序中执行任何长时间处理是否是个好主意,因为 IIS 几乎可以随时回收您的应用程序。
  • 我对新的异步东西不是很熟悉,但LongRunning 不应该在它自己的线程中发生吗? Thread.Sleep 将休眠运行该操作的同一线程。
  • 我必须同意 svick 的观点。如果您有长时间运行的事情要与请求异步,请在其他地方执行。 “典型”是一个持久队列(MSMQ、Azure、RabbitMQ 等),其他东西(Windows 服务、由任务调度程序运行的 exe、使用 Quartz.net 的应用程序等)处理它们。持久队列还意味着,如果您必须在您的 web 应用程序中进行处理,至少您不会丢失回收数据(尽管您通常会丢失进行中处理的任何中间结果)

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


【解决方案1】:

您的代码中有错误。 您必须在操作中等待对 TestAsync 的调用。 如果你不这样做,它只会返回一个“任务”对象而不运行任何东西。

public async Task<ActionResult> Test()
{
    await TestAsync();
    return View("Test");
}

在异步方法中睡眠的正确方法是调用

await Task.Delay(10000);

您必须更改 TestAsync 的签名:如果方法返回 void,则不应将其标记为异步。它必须返回 Task 。返回 void 是为了与 .net 事件兼容而保留的。

public async Task TestAsync()
{
    await LongRunning();
}

方法体是一样的。

【讨论】:

  • 你不能await TestAsync(),因为它是void-returning。
  • 如果函数被标记为异步,则不应返回 void。这是为与事件兼容而保留的。在这种情况下,只需返回 Task 而不是 void。
【解决方案2】:

显然我可以启动一个新线程来手动处理文件,但我想知道我是否可以使用 .net 4.5 引入的 async/await 机制来实现这个场景

不,你不能,因为async doesn't change the HTTP protocol

Svick 和 James 已经将正确答案发布为 cmets,为方便起见,我将其复制如下:

IIS 几乎可以随时回收您的应用程序。

如果您有长时间运行的事情要与请求异步,请在其他地方执行。 “典型”是一个持久队列(MSMQ、Azure、RabbitMQ 等),由其他东西(Windows 服务、任务调度程序运行的 exe、使用 Quartz.net 的应用程序等)处理它们。

总而言之,HTTP 为您提供一个请求和一个响应(async - 以及其他任何东西 - 都不会改变这一点)。

ASP.NET 是围绕 HTTP 请求而设计的(并做出诸如“如果没有未完成的请求,则停止该网站是安全的”这样的假设)。您可以启动一个新线程并将上传的内容保存在内存中(这是最简单的方法),但强烈不建议这样做。

针对你的情况,我建议你按照詹姆斯的建议:

  • 上传完成后,将上传内容保存到永久存储(例如 Azure 队列),并向浏览器返回“票证”。
  • 让其他进程(例如 Azure 辅助角色)处理队列,最终将其标记为完成。
  • 让浏览器使用其“票证”轮询服务器。服务器使用“票证”在持久存储中查找文件并返回它是否完整。

这有一些变化(例如,使用 SignalR 在处理完成时通知浏览器),但总体架构是相同的。

这很复杂,但它是正确的方法。

【讨论】:

  • 斯蒂芬,感谢您的回复。澄清一下,在以下情况下: 1. 我手动启动一个新线程来处理文件并将其进度写入会话对象。 2. 我在该线程完成之前将响应返回给浏览器。 3. 我使用 AJAX 调用来检查/显示进度。这个文件处理线程是否有可能在完成之前被回收?
  • 是的。整个过程将被回收。
  • 有没有什么好的资源可以用来描述如何做到这一点?有 Azure 的替代品吗?
  • 我想不出任何具体描述这一点的资源。如果您有自己的 Web 服务器,则可以使用 MSMQ 来代替 Azure 队列和 Win32 服务而不是 Azure 辅助角色来进行持久存储。
【解决方案3】:

您的 LongRunning 方法正在同步休眠 10 秒。更改它,以便在任务中发生睡眠。

【讨论】:

  • James,我已经更新了我的示例代码以包含您的建议。该问题仍然有效,即浏览器仅在 Pause() 方法完成后才收到响应
  • @filip - 如果您在 TestAsync 中 ConfigureAwait(false),行为会发生任何变化吗?在使用该同步上下文的所有待处理任务完成之前,它可能不允许完成。不过在这种情况下,你最好不要使用 await,只需启动一个正常的任务并返回
最近更新 更多