在IHttpAsyncHandler中使用Task或async / await

时间:2012-02-10 09:20:41

标签: asp.net threadpool task-parallel-library async-await ihttpasynchandler

在我想要添加线程时编写ASP.NET应用程序的开始,我可以通过3种简单的方法在ASP.NET应用程序中完成线程化:

  • 使用System.Threading.ThreadPool
  • 使用自定义委托并调用其BeginInvoke方法。
  • 借助System.Threading.Thread class。
  • 使用自定义线程

前两种方法提供了一种快速方法来为您的应用程序启动工作线程。但不幸的是,它们损害了应用程序的整体性能,因为它们使用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而不是来自线程池

提前感谢您的帮助。

托马斯

6 个答案:

答案 0 :(得分:16)

这种情况是Taskasyncawait真正闪耀的地方。这是相同的示例,重构为充分利用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的更多信息,我的博客上有intro post

答案 1 :(得分:7)

我一直在网上寻找信息几天。让我总结一下我到现在所发现的东西:

ASP.NET ThreadPool事实

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

  • Andres继续...... 如果您尝试异步执行某些同步代码或您自己的库代码,该代码可能使用额外的ThreadPool线程,除非您明确使用IOCP ThreadPool或您自己的ThreadPool。

但据我所知,你不能选择你想要使用IOCP线程,并且正确实现threadPool是不值得的。我怀疑有人做了一个已经存在的更好的。

  • ASP.NET使用公共语言运行时(CLR)线程池中的线程来处理请求。只要线程池中有可用的线程,ASP.NET就可以轻松调度传入的请求。

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

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

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

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

  • 如果请求的计算成本低廉,那么并行性可能是一种不必要的开销。

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

    < / LI>

我应该创建新主题吗?

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

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

和TPL?

  • TPL可以适应使用流程中的可用资源。如果服务器已经加载,则TPL可以使用少至一个工作程序并进行前进。如果服务器大部分是免费的,那么他们可以增加使用ThreadPool可以使用的工作人员数量。

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

<强>参考

答案 2 :(得分:3)

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

当async / await不会消耗额外的ThreadPool线程时? 仅在您使用BCL异步方法(如WebClient异步扩展提供的方法)时才使用IOCP线程执行IO绑定操作。

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

谢谢, 安德烈斯。

答案 3 :(得分:1)

Parallel Extensions团队在使用TPL和ASP.NET时a blog post解释了TPL和PLINQ如何使用ASP.NET ThreadPool。该帖子甚至还有一个决策图表,可以帮助您选择正确的方法。

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

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

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

答案 4 :(得分:1)

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

请阅读这篇优秀的帖子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 threadPool)中的另一个线程中运行昂贵的计算代码。

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

答案 5 :(得分:0)

SignalR项目中的.NET 4.0有一个很好的HttpTaskAsyncHandler实现。您可能希望将其删除:http://bit.ly/Jfy2s9