【问题标题】:Understanding ASP.NET asynchronous processing了解 ASP.NET 异步处理
【发布时间】:2011-12-15 08:26:38
【问题描述】:

到目前为止,我读过的每篇关于 ASP.NET 异步编程的文章都指出,当我们向数据库(或其他 IO)发出请求时,请求线程将返回到 ThreadPool (Page.AddOnPreRenderCompleteAsync())。据我了解,所有异步 IO 都依赖于 .NET 功能,允许我们异步调用任何委托。但是根据 MSDN 用BeginInvoke()/EndInvoke() 调用代表仍然涉及ThreadPool。这意味着如果我们将当前的 http 线程返回给ThreadPool(通过调用Page.AddOnPreRenderCompleteAsync()),我们需要另一个线程来执行我们的委托。有什么意义?

我无法解决的另一个问题是当异步调用结束并且请求处理继续时会发生什么。最有可能在其他线程上,对吧? (因为原始线程可能会被其他请求重用)。这意味着我们失去了.CurrentPrincipal、.CurrentCulture 和其他线程上下文。那么又有什么意义呢?

更新:MSDN 非常清楚在标准BeginInvoke()/EndInvoke() 模式中使用ThreadPool:

将回调方法的委托传递给 BeginInvoke。方法是 异步调用完成时在 ThreadPool 线程上执行。 回调方法调用 EndInvoke。

这对于 .NET 1.1 - .NET 4 是正确的。

【问题讨论】:

    标签: asp.net asynchronous threadpool


    【解决方案1】:

    This article 涵盖了基础知识,并提供了一个可供您使用的可下载示例解决方案。

    【讨论】:

    • 非常有用,尤其是允许“模拟、文化和 HttpContext.Current 自动流向完成事件处理程序”的技术。
    【解决方案2】:

    更新: 正在发生的事情的细节很重要,但我将从简单的答案开始。您的主要问题是如何解决获取线程本地状态信息的问题?如果您已将 Begin 和 EndInvoke 方法编写为 page 背后代码中的方法,则您可以访问 Page 上的任何数据成员。因此,只需在后面的代码中创建一个私有数据成员 IPrincipal principal_ 和 CultureInfo culture_ 数据成员,并在 page_load 方法中初始化它们。然后,您可以在 Begin 和 EndInvoke 方法中直接访问它们。

    现在细节中的魔鬼有望回答您的“重点是什么”问题: 我将首先讨论线程池,因为我认为您不清楚线程与它们的关系。一个 ThreadPool 可能有 1 个线程或 N 个工作线程在其上工作。将委托添加到线程池时,它通常不会实际创建新线程。分配给池的线程会发生什么并没有做任何事情......如果线程在其时间到上下文切换之前完成,它可以接受另一个任务并运行它。线程池中的一个线程不限于单个任务。一个线程可能运行多个任务,这些任务可以顺序调用多个异步处理程序。线程池外的线程可能正在调用异步处理程序这一事实无关紧要。以这种方式,可以使用相对较少的线程和较少的上下文切换来运行许多委托。大多数情况下,当在异步模式中使用线程池时,作业的详细信息会查询阻塞操作是否已完成,如果已完成,则调用下一个回调以继续或完成异步操作的工作。您可能有数千个处理程序阻塞和轮询,并且只有少数线程忙于处理它们,因为大多数时候只有少数线程有工作要做。它很有效,这就是重点!

    需要注意的是,调用 Async Handler 方法 BeginInvoke/EndInvoke 并不意味着它使用线程或涉及 ThreadPool。如果 IO(或任何工作)足够快,则很多时候根本没有将任何内容推送到线程池上。实现可以选择从不产生线程或将作业推送到线程池中。如果它不做这些事情,那么异步处理程序将永远不会切换上下文,并且它根本不会与调用线程并行运行。没关系,异步方法只需要做尽可能多的工作,并且只需要在必须等待某些事情时使用线程池。异步方法和回调可能永远不会被推送到单独的线程中。这是一个重要的区别,它使缓冲异步 IO 调用等情况保持高效,无需任何上下文切换。

    The Book C# 4.0 in a Nutshell 是一个优秀的资源,很好地解释了异步模式和方法。

    您使用 MSDN 链接所做的澄清具有误导性,因为您忽略了其完整上下文:

    本主题中的代码示例演示了四种常见的使用方式 BeginInvoke 和 EndInvoke 进行异步调用。打电话后 BeginInvoke 您可以执行以下操作:

    做一些工作然后调用 EndInvoke 阻塞直到调用 完成。

    使用 System.IAsyncResult.AsyncWaitHandle 获取 WaitHandle 属性,使用它的 WaitOne 方法阻止执行,直到 WaitHandle 发出信号,然后调用 EndInvoke。

    轮询 BeginInvoke 返回的 IAsyncResult 以确定何时 异步调用完成,再调用EndInvoke。

    将回调方法的委托传递给 BeginInvoke。方法是 异步调用完成时在 ThreadPool 线程上执行。 回调方法调用 EndInvoke。

    正如您所看到的,您引用的要点与示例和几个选项中的一个选项相关,因此不能保证任何方法都在 ThreadPool 上执行。此外,您在 cmets 中与我的链接解释了 AddOnPreRenderCompleteAsync 的情况,它强制线程池上的异步处理程序,但这仍然是他们的示例的情况。 (Article)

    但重要的是,您说您仔细检查了您的方法(哪个?)是在不同的线程上调用的。这可能是您使用不同的异步操作注册的回调方法?如果您从自己的处理程序中将 BeginInvoke 调用到不同的异步方法,那可能会在另一个线程中运行。在进行任何此类调用之前,您很可能仍处于与页面关联的线程中。即使您调用了一个异步方法,该方法确实通过在某个时候将某些内容推送到线程池来实现自身,如果该操作足够快,它实际上可能会跳过该步骤!我花了这么长时间来解释以不同的原因结束的相同结果的唯一原因是,您了解异步调用的行为不需要线程池,但您不能依赖上下文切换与否,异步调用的重点不是并行性而是效率。

    【讨论】:

    • 我没有提到 Tasks 也没有提到 PLinq,因为我想先了解基础知识,只是 .NET 1.1 - 2.0 中可用的标准方法,所以你的回答对我来说真的让事情变得更加复杂:(跨度>
    • 至于第二部分 - 我发现我们在 PreRender() 中返回的线程确实不一样(msdn.microsoft.com/en-us/magazine/cc163463.aspx),因此上下文丢失了。无论如何,感谢您的努力 +1。
    • 好的,你需要清楚你正在寻找的 .NET 版本有问题,这样其他人就不会犯这个错误。
    • 感谢您通过 MSDN 文章更新 PreRender。我离开了对这种情况模棱两可的 API 文档。我会更新我的答案。
    【解决方案3】:

    这意味着我们失去了 .CurrentPrincipal、.CurrentCulture 和其他线程上下文。那么又有什么意义呢?

    上下文切换代价高昂,但总体而言,该概念是有效的,同时线程总数保持在推荐的阈值内。

    【讨论】:

    • 如果我们将一个请求线程返回给 ThreadPool 并得到另一个(来自 ThreadPool)来处理异步工作?所以在每个时间点总是有一个线程忙于工作,我们得到了 2 个上下文切换。我错过了什么吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-28
    相关资源
    最近更新 更多