3

在 ASP.NET MVC 中,我有一个 Action,它接受用户输入的电子邮件地址列表,对其进行验证,然后将电子邮件的 HTML 和文本版本发送给每个收件人。然后用户被重定向到感谢页面。因为没有必要等待重定向到感谢页面,直到发送所有电子邮件,我正在使用 Task.Factory.StartNew 方法来启动一个实际发送电子邮件的新任务,它工作正常;用户立即被重定向到感谢页面,并且电子邮件消息在单独的线程上发送。所以一切都按我的意愿工作,但是我有以下关于多线程的问题。前几天看了很多帖子,还是没找到答案

  1. 任务库使用线程池中的线程

    如果您使用的是任务库,则您正在使用 ASP.NET 线程池中的线程创建新线程。这意味着少一个线程可用于为应用程序提供其他 ASP.NET 请求。因此,通过使用任务库,您并没有通过将任务卸载到其他一些操作系统线程来优化 ASP.NET 线程的使用,只有用户体验更好,但任务库使用了另一个可用于服务其他 ASP.NET 请求的线程。所以唯一的结果是用户不需要等待。

  2. 手动螺纹

    如果你真的想使用操作系统线程,你必须显式地启动新线程。但即使您启动新的操作系统线程,您也需要拥有多核或多处理器的机器才能真正看到应用程序可扩展性的改进。

  3. 后台线程池

    一些帖子讨论了 ASP.NET 线程池和用于后台任务的单独应用程序的后台线程池。换句话说,每个 ASP.NET 应用程序都有一个线程池来服务应用程序请求,而另一个线程池来服务后台任务。我不认为这是真的,我认为每个 ASP.NET 应用程序只有一个线程池,并且该池中的线程用于同时服务:应用程序请求和后台任务。当通常只有一个线程在运行(UI 线程)并且您必须显式启动新线程时,也许对于 Windows 窗体应用程序可以这样说。但是 ASP.NET 在其基础上是多线程的。

以下是问题:

  1. 我在 Dino Esposito 的 Programming ASP.NET MVC 2 中阅读了异步 MVC 控制器。他写了关于异步控制器如何使用 OS 线程来执行长时间运行的任务,因此最初为 ASP.NET 请求提供服务的 ASP.NET 线程现在可以自由地为其他请求提供服务。

    虽然我在这里不需要异步控制器,但我的问题是如何在我的示例中使用这样的操作系统线程。我是否必须显式地启动单独的线程,或者这是否也可以通过任务库以某种方式实现?

  2. 即使我将这些任务卸载到某些操作系统线程,如果机器只有一个处理器,会有什么好处吗?我认为多核机器对于真正提高应用程序的可扩展性是必要的。

4

2 回答 2

4

您遇到的真正问题是 ASP.NET 可以随时回收您的 AppDomain。发生这种情况时,您的后台线程将因极端偏见而中止。使用线程池线程还是启动自己的线程都没有关系——如果 ASP.NET 不“知道”线程,它可以并且会破坏 AppDomain 并带走您的线程。

“正确”的解决方案是拥有一个单独的 Windows 服务进程,即 WCF 服务器。然后,您的 Web 应用程序可以将命令发送到执行它们的服务 - 例如发送一些电子邮件。

快速而肮脏的方法是在您的网络应用程序中隐藏一个发送电子邮件的操作。启动对该 Action 的异步请求,然后返回“感谢”页面而不等待结果。ASP.NET 并不关心请求是否来自您的应用程序 - 它只是将其视为另一个请求。因为 ASP.NET “知道”该请求,所以它不会中止它。

于 2012-04-09T11:03:58.707 回答
1

[...] 他写到异步控制器如何使用 OS 线程来执行长时间运行的任务,因此最初为 ASP.NET 请求提供服务的 ASP.NET 线程现在可以自由地为其他请求提供服务。

嗯,这取决于你在那个异步控制器中有什么代码。它可以在 OS 上执行一些代码Thread,但不是必须的。通常发生的情况是您启动了一些在运行时不使用任何线程的异步操作。当它完成时,结果会在某个ThreadPool线程上执行。

虽然我在这里不需要异步控制器,但我的问题是如何在我的示例中使用这样的操作系统线程。我是否必须显式地启动单独的线程,或者这是否也可以通过任务库以某种方式实现?

您可以使用TaskCreationOptions.LongRunning. 这不能保证会启动一个新线程,但实际上它确实如此。

即使我将这些任务卸载到某些操作系统线程,如果机器只有一个处理器,会有什么好处吗?我认为多核机器对于真正提高应用程序的可扩展性是必要的。

如果该线程同步执行某些 IO 绑定操作,则可能会有好处。例如,发送电子邮件所花费的大部分时间很可能不是使用 CPU,而是等待服务器响应。如果是这种情况,使用另一个线程(无论是单独的线程还是来自 a 的线程ThreadPool)可能是有益的。当然,如果您异步执行该操作会更好,这不会阻塞任何线程,但这可能会使您的代码更加复杂(并且可能存在甚至不可能的情况)。

关于您写的其他内容:

如果您使用的是任务库,则您正在使用 ASP.NET 线程池中的线程创建新线程。这意味着少一个线程可用于为应用程序服务其他 ASP.NET 请求

是的,在正常情况下(如果您使用默认值TaskScheduler且未指定TaskCreationOptions.LongRunning)。但是,如果您遇到问题,更好的解决方案可能是增加 ThreadPool 限制,而不是Thread自己为短期运行的任务创建 s。那是因为创建和销毁线程是一项昂贵的操作,如果性能对您很重要,您应该避免它。

[E] 每个 ASP.NET 应用程序都有一个线程池来服务应用程序请求,另一个线程池来服务后台任务。我不认为这是真的,我认为每个 ASP.NET 应用程序只有一个线程池,并且该池中的线程用于同时服务:应用程序请求和后台任务。

它比这稍微复杂一些,但是是的,ASP.NET 请求和 ThreadPool 任务(使用默认选项开始ThreadPool.QueueUserWorkItem()或通过Task使用默认选项开始)在同一个线程池上运行。

于 2012-04-09T11:15:14.363 回答