我尝试了一下.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();
委托中的ctx
为null
。现在,据我所知,在使用TaskScheduler.FromCurrentSynchronizationContext()
任务调度器时,应该恢复上下文。那它为什么不在这里呢?(顺便说一下,我可以看到委托在同一线程上同步执行)。
此外,在msdn中,TaskScheduler.FromCurrentSynchronizationContext()
的行为应如下所示:
在返回的调度程序中排队的所有任务实例都将通过调用该上下文上的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上异步编程的理解。
发布于 2012-12-31 13:23:02
您的观察结果似乎是正确的,这有点令人费解。您将指定为"TaskScheduler.FromCurrentSynchronizationContext()".这将关联一个新的"SynchronizationContextTaskScheduler“。现在,如果你看一下这个类,它使用:
如果任务调度器有权访问相同的“同步上下文”,那么应该引用"LegacyAspNetSychronizationContext“。所以看起来HttpContext.current肯定不应该为空。
在第二种情况下,当您使用SychronizationContext (参见:MSDN Article)时,线程的上下文与任务共享:
“SynchronizationContext的另一个方面是每个线程都有一个”当前“上下文。一个线程的上下文不一定是唯一的;它的上下文实例可以与其他线程共享。”
在这种情况下,SynchronizationContext.Current是由LegacyAspNetSychronizationContext提供的,并且在内部具有对HttpApplication的引用。
当Post方法必须调用已注册的回调时,它会调用HttpApplication.OnThreadEnter,这最终会导致将当前线程的上下文设置为HttpCurrent.Context:
这里引用的所有类在框架中都被定义为内部类,这使得进一步研究有点困难。
PS:说明两个SynchornizationContext对象实际上都指向"LegacyAspNetSynchronizationContext":
发布于 2012-12-24 18:08:44
前段时间我在谷歌上搜索HTTPContext的信息。我发现了这个:
http://odetocode.com/articles/112.aspx
这是关于线程和HTTPContext的。有一个很好的解释:
CallContext提供了一种与线程本地存储极其相似的服务(除了CallContext可以在远程调用期间执行一些额外的魔术)。线程本地存储是一个概念,其中应用程序域中的每个逻辑线程都有一个唯一的数据槽来保存特定于其自身的数据。线程不共享数据,并且一个线程不能修改另一个线程的本地数据。在选择要执行传入请求的线程之后,ASP.NET将对当前请求上下文的引用存储在线程的本地存储中。现在,无论线程在执行时位于何处(业务对象、数据访问对象),上下文都在附近,并且很容易检索。
了解了上面的情况,我们可以声明如下:如果在处理请求时,执行移动到不同的线程(例如通过QueueUserWorkItem或异步委托),HttpContext.Current将不知道如何检索当前上下文,并将返回null。您可能认为解决此问题的一种方法是将引用传递给工作线程
因此,您必须通过某个变量创建对HTTPContext.Current引用,并且此变量将从您将在代码中创建的其他线程中寻址。
发布于 2012-12-29 18:54:04
你的结果很奇怪--你确定没有其他事情发生吗?
您的第一个示例(使用Task
)只能运行,因为Task.Wait()
可以“内联”运行任务主体。
如果您在任务lambda中放置一个断点并查看调用堆栈,您将看到lambda是从Task.Wait()
方法内部调用的-没有并发。因为任务是通过正常的同步方法调用来执行的,所以HttpContext.Current
必须返回与控制器方法中其他任何地方相同的值。
您的第二个示例(使用SynchronizationContext.Post
)将会死锁,并且您的lambda将永远不会运行。
这是因为您使用的是一个AutoResetEvent
,它对您的Post
一无所知。对WaitOne()
的调用将阻塞线程,直到AutoResetEvent
为Set
。同时,SynchronizationContext
正在等待线程释放,以便运行lambda。
由于线程在WaitOne
中被阻塞,因此发布的lambda将永远不会执行,这意味着AutoResetEvent
将永远不会被设置,这意味着WaitOne
永远不会得到满足。这是一个死锁。
https://stackoverflow.com/questions/13971595
复制相似问题