更新:
正在发生的事情的细节很重要,但我将从简单的答案开始。您的主要问题是如何解决获取线程本地状态信息的问题?如果您已将 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 调用到不同的异步方法,那可能会在另一个线程中运行。在进行任何此类调用之前,您很可能仍处于与页面关联的线程中。即使您调用了一个异步方法,该方法确实通过在某个时候将某些内容推送到线程池来实现自身,如果该操作足够快,它实际上可能会跳过该步骤!我花了这么长时间来解释以不同的原因结束的相同结果的唯一原因是,您了解异步调用的行为不需要线程池,但您不能依赖上下文切换与否,异步调用的重点不是并行性而是效率。