21

我正在玩弄 .NET 的异步功能,但遇到了一个我无法真正解释的情况。在同步 ASP.NET MVC 控制器中执行以下代码时

var t = Task.Factory.StartNew(()=>{
        var ctx = System.Web.HttpContext.Current;
        //ctx == null here
},
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext()
);

t.Wait();

ctxnull委托内。TaskScheduler.FromCurrentSynchronizationContext()现在据我了解,使用任务调度程序时应该恢复上下文。那么为什么不在这里呢?(顺便说一句,我可以看到委托在同一个线程上同步执行)。

此外,从msdn, a 的TaskScheduler.FromCurrentSynchronizationContext()行为应如下所示:

所有排队到返回调度程序的 Task 实例都将通过在该上下文中调用 Post 方法来执行。

但是,当我使用此代码时:

var wh = new AutoResetEvent(false);

SynchronizationContext.Current.Post(s=> {
    var ctx = System.Web.HttpContext.Current;
    //ctx is set here
    wh.Set();
    return;
},null);

wh.WaitOne();

上下文实际上是设置的。

我知道这个例子有点做作,但我真的很想了解会发生什么来增加我对 .NET 上的异步编程的理解。

4

3 回答 3

8

您的观察似乎是正确的,这有点令人费解。 您将调度程序指定为“TaskScheduler.FromCurrentSynchronizationContext()”。这将关联一个新的“SynchronizationContextTaskScheduler”。现在,如果您查看它使用的此类: 在此处输入图像描述

因此,如果任务调度程序可以访问相同的“同步上下文”并且应该引用“LegacyAspNetSychronizationContext”。因此,显然 HttpContext.current 不应该为空。

在第二种情况下,当您使用 SychronizationContext(请参阅:MSDN 文章)时,线程的上下文与任务共享:

“SynchronizationContext 的另一个方面是每个线程都有一个‘当前’上下文。线程的上下文不一定是唯一的;它的上下文实例可以与其他线程共享。”


SynchronizationContext.Current 在这种情况下由 LegacyAspNetSychronizationContext 提供,内部有对 HttpApplication 的引用。

当 Post 方法必须调用注册的回调时,它会调用 HttpApplication.OnThreadEnter,这最终导致将当前线程的上下文设置为 HttpCurrent.Context:

在此处输入图像描述

此处引用的所有类都在框架中定义为内部类,这使得进一步调查变得有点困难。

PS:说明两个 SynchornizationContext 对象实际上都指向“LegacyAspNetSynchronizationContext”: 在此处输入图像描述

于 2012-12-31T05:23:01.830 回答
1

前段时间我在谷歌上搜索 HTTPContext 信息。我发现了这个:

http://odetocode.com/articles/112.aspx

它是关于线程和 HTTPContext 的。有很好的解释:

CallContext 提供了与线程本地存储极为相似的服务(除了 CallContext 可以在远程调用期间执行一些额外的魔法)。线程本地存储是一个概念,其中应用程序域中的每个逻辑线程都有一个唯一的数据槽来保存特定于自身的数据。线程不共享数据,并且一个线程不能将数据本地修改到不同的线程。ASP.NET 在选择一个线程来执行传入请求后,会将对当前请求上下文的引用存储在线程的本地存储中。现在,无论线程在执行时去哪里(业务对象、数据访问对象),上下文都在附近并且很容易检索。

了解上述内容后,我们可以说明以下内容:如果在处理请求时,执行移动到不同的线程(通过 QueueUserWorkItem 或异步委托,作为两个示例),HttpContext.Current 将不知道如何检索当前上下文,并且将返回空值。您可能认为解决该问题的一种方法是将引用传递给工作线程

因此,您必须通过某个变量创建对 HTTPContext.Current 的引用,并且该变量将从您将在代码中创建的其他线程中寻址。

于 2012-12-24T10:08:43.623 回答
1

你的结果很奇怪——你确定没有其他事情发生吗?


您的第一个示例( with Task)仅适用,因为Task.Wait()可以“内联”运行任务主体。

如果你在任务 lambda 中放置一个断点并查看调用堆栈,你会看到 lambda 是从方法内部调用的Task.Wait()——没有并发。由于任务是通过正常的同步方法调用执行的,HttpContext.Current因此必须返回与控制器方法中其他任何地方相同的值。


您的第二个示例( with SynchronizationContext.Post)将死锁,并且您的 lambda 将永远不会运行。

这是因为您使用的是AutoResetEvent,它对您的Post. 调用WaitOne()将阻塞线程,直到AutoResetEventis Set。同时,SynchronizationContext等待线程空闲以便运行 lambda。

由于线程被阻塞在 中WaitOne,因此发布的 lambda 将永远不会执行,这意味着AutoResetEvent永远不会设置 ,这意味着WaitOne永远不会满足。这是一个僵局。

于 2012-12-29T10:54:04.367 回答