17

自从我想添加线程开始编写 ASP.NET 应用程序以来,有 3 种简单的方法可以在我的 ASP.NET 应用程序中完成线程:

  • 使用System.Threading.ThreadPool.
  • 使用自定义委托并调用其BeginInvoke方法。
  • 在类的帮助下使用自定义线程System.Threading.Thread

前两种方法提供了一种为您的应用程序启动工作线程的快速方法。但不幸的是,它们会损害应用程序的整体性能,因为它们使用 ASP.NET 用来处理 HTTP 请求的同一池中的线程

然后我想用一个新的 Task 或者 async/await 来写IHttpAsyncHandler. 你可以找到一个例子是 Drew Marsh 在这里解释的:https ://stackoverflow.com/a/6389323/261950

我的猜测是,使用 Task 或 async/await 仍然会消耗 ASP.NET 线程池中的线程,并且出于明显的原因我不想要。

您能否告诉我是否可以在后台线程上使用 Task (async/await),例如使用System.Threading.Thread而不是线程池

在此先感谢您的帮助。

托马斯

4

6 回答 6

20

这种情况是Task,asyncawait真正闪耀的地方。这是相同的示例,经过重构以充分利用async(它还使用我的AsyncEx库中的一些帮助程序类来清理映射代码):

// First, a base class that takes care of the Task -> IAsyncResult mapping.
// In .NET 4.5, you would use HttpTaskAsyncHandler instead.
public abstract class HttpAsyncHandlerBase : IHttpAsyncHandler
{
    public abstract Task ProcessRequestAsync(HttpContext context);

    IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        var task = ProcessRequestAsync(context);
        return Nito.AsyncEx.AsyncFactory.ToBegin(task, cb, extraData);
    }

    void EndProcessRequest(IAsyncResult result)
    {
        Nito.AsyncEx.AsyncFactory.ToEnd(result);
    }

    void ProcessRequest(HttpContext context)
    {
        EndProcessRequest(BeginProcessRequest(context, null, null));
    }

    public virtual bool IsReusable
    {
        get { return true; }
    }
}

// Now, our (async) Task implementation
public class MyAsyncHandler : HttpAsyncHandlerBase
{
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        using (var webClient = new WebClient())
        {
            var data = await webClient.DownloadDataTaskAsync("http://my resource");
            context.Response.ContentType = "text/xml";
            context.Response.OutputStream.Write(data, 0, data.Length);
        }
    }
}

(如代码中所述,.NET 4.5 有一个HttpTaskAsyncHandler类似于我们HttpAsyncHandlerBase上面的)。

真正酷的是async它在执行后台操作时不占用任何线程:

  • 一个 ASP.NET 请求线程启动请求,并开始使用WebClient.
  • 在下载过程中,await实际从方法中返回async,离开请求线程。该请求线程返回到线程池 - 留下 0()个线程为该请求提供服务。
  • 当下载完成时,该async方法在请求线程上恢复。该请求线程仅用于编写实际响应。

这是最佳的线程解决方案(因为需要一个请求线程来编写响应)。

原始示例还以最佳方式使用线程 - 就线程而言,它与async基于 - 的代码相同。但 IMO 的async代码更容易阅读。

如果您想了解更多关于的信息,我的博客上async有一篇介绍文章。

于 2012-02-10T14:54:32.933 回答
7

这几天一直在网上找资料。让我总结一下我到目前为止的发现:

ASP.NET 线程池事实

  • 正如 Andres 所说:当 async/await 不会消耗额外的 ThreadPool 线程时?仅在您使用 BCL 异步方法的情况下。使用 IOCP 线程执行 IO 绑定操作。

  • Andres继续... _ _ _

但据我所知,您无法选择是否要使用 IOCP 线程,并且正确实现 threadPool 是不值得的。我怀疑有人做得更好,已经存在。

  • ASP.NET 使用公共语言运行时 (CLR) 线程池中的线程来处理请求。只要线程池中有可用的线程,ASP.NET 就可以毫无问题地分派传入的请求。

  • 异步delegates使用 ThreadPool 中的线程。

什么时候应该开始考虑实现异步执行?

  • 当您的应用程序执行相对冗长的 I/O 操作(数据库查询、Web 服务调用和其他 I/O 操作)时

  • 如果你想做 I/O 工作,那么你应该使用 I/O 线程(I/O 完成端口),特别是你应该使用你正在使用的任何库类支持的异步回调。Begin他们的名字以and开头End

  • 如果请求在计算上处理起来很便宜,那么并行性可能是不必要的开销。

  • 如果传入的请求率很高,那么增加更多的并行性可能不会带来什么好处,实际上可能会降低性能,因为传入的工作率可能高到足以让 CPU 保持忙碌。

我应该创建新线程吗?

  • 避免像避免瘟疫一样创建新线程。

  • 如果您实际上排队了足够多的工作项以防止 ASP.NET 处理进一步的请求,那么您应该饿死线程池!如果您同时运行数百个 CPU 密集型操作,那么在机器已经超载的情况下,让另一个工作线程为 ASP.NET 请求提供服务会有什么好处。

TPL 呢?

  • TPL 可以适应在进程中使用可用资源。如果服务器已经加载,TPL 可以使用最少一个工作人员并向前推进。如果服务器大部分是免费的,它们可以增长到使用线程池可以腾出的尽可能多的工作人员。

  • 任务使用线程池线程来执行。

参考

于 2012-02-24T10:19:22.280 回答
3

说“0(零)个线程将为这个请求提供服务”并不完全准确。我认为您的意思是“来自 ASP.NET 线程池”,在一般情况下这是正确的。

什么时候 async/await 不会消耗额外的 ThreadPool 线程?仅在您使用 BCL 异步方法(如 WebClient 异步扩展提供的方法)的情况下,该方法使用 IOCP 线程来执行 IO 绑定操作。

如果您尝试异步执行某些同步代码或您自己的库代码,则该代码可能会使用额外的线程池线程,除非您明确使用 IOCP 线程池或您自己的线程池。

谢谢,安德烈斯。

于 2012-02-15T16:43:48.377 回答
1

Parallel Extensions 团队有一篇关于将 TPL 与 ASP.NET 结合使用的博客文章,其中解释了 TPL 和 PLINQ 如何使用 ASP.NET ThreadPool。该帖子甚至有一个决策图来帮助您选择正确的方法。

简而言之,PLINQ 使用线程池中的每个核心一个工作线程来执行整个查询,如果流量很大,这可能会导致问题。

另一方面,Task 和 Parallel 方法将适应进程的资源,并且可以使用最少一个线程进行处理。

就 Async CTP 而言,async/await 构造与直接使用 Tasks 之间在概念上几乎没有区别。编译器使用一些魔法在幕后将等待转换为任务和继续。最大的不同是你的代码更干净,更容易调试。

于 2012-02-16T14:39:55.970 回答
1

要考虑的另一件事是 async/await 和 TPL(任务)不是一回事。

请阅读这篇出色的文章 http://blogs.msdn.com/b/ericlippert/archive/2010/11/04/asynchrony-in-c-5-0-part-four-it-s-not-magic.aspx了解为什么 async/await 并不意味着“使用后台线程”。

回到我们这里的主题,在您想要在 AsyncHandler 中执行一些昂贵的计算的特定情况下,您有三个选择:

1) 将代码留在 Asynchandler 中,因此昂贵的计算将使用 ThreadPool 中的当前线程。2) 使用 Task.Run 或 Delegate 在另一个 ThreadPool 线程中运行昂贵的计算代码 3) 从您的自定义线程池(或 IOCP 线程池)中的另一个线程中运行昂贵的计算代码。

第二种情况对您来说可能就足够了,具体取决于您的“计算”过程运行多长时间以及您有多少负载。安全选项是#3,但在编码/测试方面要贵得多。我还建议始终将 .NET 4 用于使用异步设计的生产系统,因为 .NET 3.5 中有一些硬性限制。

于 2012-02-16T14:56:47.433 回答
1

SignalR 项目中有一个很好的 .NET 4.0 HttpTaskAsyncHandler 实现。您可能想检查一下:http ://bit.ly/Jfy2s9

于 2012-04-21T12:20:32.050 回答