none
关于线程中Context Flow问题 RRS feed

  • 问题

  • 先看一小段程序

    class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Main Thread id : {0}", Thread.CurrentThread.ManagedThreadId);
                CallContext.LogicalSetData("LogicalData","data");
                new FileDialogPermission(FileDialogPermissionAccess.OpenSave).Deny();

                using (AsyncFlowControl afc = ExecutionContext.SuppressFlow())
                {
                    WaitCallback wc = new WaitCallback(PrintLogicData);
                    ThreadPool.QueueUserWorkItem(wc);
                    wc.EndInvoke(wc.BeginInvoke(null, null, null));
                }
                Console.ReadLine();
            }

            static void PrintLogicData(object state)
            {
                Console.WriteLine("Thread id : {0}",Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine("Logical Data : {0}", CallContext.LogicalGetData("LogicalData"));
                try
                {
                    new FileDialogPermission(FileDialogPermissionAccess.OpenSave).Demand();
                    Console.WriteLine("Successfully demanded {0}", Environment.NewLine);
                }
                catch (Exception)
                {
                    Console.WriteLine("Demand for FileDialogPermission failed {0}", Environment.NewLine);
                }
            }
        }

    在我的机器上运行的结果是

    Main Thread id : 9

    Thread id : 6

    Logical Data:

    Successfully demanded

     

    Thread id : 10

    Logical Data: data

    Successfully demanded

     

    在抑制上下文流动后,通过显示调用ThreadPool的方法执行的结果是有权限访问FileDialog并且不能访问到CallContext中的数据,这个结果是符合预期的。但是在通过委托异步调用(其实它的本质也是通过线程池中的线程来执行)的结果是有权限访问FileDialog,这说明上下文是被抑制流动了,否则它就没有权限因为在主线程中已经设置无权限访问FileDialog。这个结果也是符合预期的,但奇怪的是既然上下文是被抑制流动了它为什么可以访问到CallContext中的数据? 如果这也是正常的那么为什么直接使用ThreadPool却不能访问?


    AndersTan
    2011年10月11日 7:02

答案

  • 我已经收到了从 CLR Team 的回复。他是这么说的:

    This surprised me as well, so I had a quick look at the code.  Async delegate invocation is implemented using some Remoting infrastructure, which turns out to capture and restore the logical CallContext explicitly, regardless of whether the overall ExecutionContext flow is suppressed.  I’m not sure what the rationale for this is, but at this point we have to call it a “feature” because changing it would break any code that relies on the behavior.

    BTW, that remoting infrastructure is actually quite expensive.  For that reason, I usually recommend avoiding async delegate invocation where possible, independent of this issue.  As of .NET 4, the Task type provides a much more efficient way to execute code asynchronously; prior to .NET 4, QueueUserWorkItem may be your best bet.

    大概就是说,这东西他也没想到有这个结果,可能就是个 Bug,异步的 Delegate 是利用 Remoting 架构实现的,里面有一些对 LogicData 的特殊处理,而不管是否改变/抑制了 Control Flow。然后建议是,尽量使用 Task 或者 ThreadPool 来处理异步,避免直接调用异步的 Delegate。

    希望这个回答对您有帮助。


    Mark Zhou
    • 已标记为答案 Anders Tan 2011年10月18日 13:17
    2011年10月18日 10:39

全部回复

  • 我觉得这个问题是个非常好的问题。

    我的建议是您先把代码放到 Win Forms 或者 WPF 程序里面去跑跑。Console App 对于线程有一定的局限性。很有可能 Delegate 没有在 Thread Pool 上跑。上个星期我们就遇到了 new 了 100 个 Task 但是在 Windows Task Manager 中只看到 1 个的问题。

    如果换了环境,问题仍然依旧诡异,请再联系我,我帮您问一下 CLR Team 这到底怎么回事。我也很好奇。

    理论上 LogicalContext 是 ExecutionContext 的一部分,也就是任何 Thread 都会有自己的 LogicalData,最后的输出结果貌似确实很诡异。


    Mark Zhou
    • 已编辑 mazhou 2011年10月11日 15:17
    2011年10月11日 11:15
  • 参考 http://www.wintellect.com/CS/blogs/jeffreyr/archive/2010/09/27/logical-call-context-flowing-data-across-threads-appdomains-and-processes.aspx

     

    Many developers are familiar with Thread Local Storage (TLS) as a mechanism allowing you to associate a piece of data with a thread. I usually discourage people from using TLS because I recommend architecting applications to perform their operations using thread pool threads. TLS doesn't work with the thread pool because different thread pool threads all take part in a since workflow or operation sequence. However, sometimes people would like to associate some data with a workflow that multiple threads participate in. The .NET Framework has a little-known facility that allows you to associate data with a "logical" thread-of-execution. This facility is called logical call context and it allows data to flow to other threads, AppDomains, and even to threads in other processes.

     

    关于TLS 参考 http://en.wikipedia.org/wiki/Thread-local_storage

     

    简单的说就是为了解决TLS和线程绑定 所以出现了这个LogicalSetData

    LogicSetData和 SetData的最大区别就是 前者是按照逻辑上下文的,可以跨域,线程和进程

     


    • 已编辑 wenjin.xu 2011年10月11日 15:42
    2011年10月11日 15:39
  • mazhou 首先谢谢你的回复,本以为这两天北京微软在搞技术大会大家都去参加大会了,能看到你的回复很高兴。

    至于你说delegate没有在Thread Pool上跑的可能性,我觉得肯定是在threadpool中的线程来执行的,但是为了严谨我还是特地做了测试,也很简单,我就在PrintLogicData方法中加了一句 Console.WriteLine("Is current thread in thread pool? {0}",Thread.CurrentThread.IsThreadPoolThread); 运行的结果是

     

    Main Thread id : 9

    Thread id : 6

    Is current thread in thread pool? True

    Logical Data:

    Successfully demanded

     

    Thread id : 10

    Is current thread in thread pool? True

    Logical Data: data

    Successfully demanded

     

    显然Delegate的异步执行还是在ThreadPool中的线程来执行的,然后我又把代码放在windows form中执行,结果还是和Console中的一样。我的测试环境是win7 ultimate + vs2010 sp1, 我也是在为公司部门内多线程讲座写Demo的时候碰到的这个问题,希望你能和CLR Team反映一下这个问题并能告知其缘由,我也能跟部门同事做个合理的解释。再次感谢。(很羡慕你是 MSFT,能和CLR Team直接沟通:))


    AndersTan
    2011年10月11日 16:58
  • wenjin.xu,谢谢你的回复

    你给的连接和说明我都看了,是不错的参考。不过可能你没看清楚我的提问,我不是对一个线程中的ExecutionContext能够传递到另外一个辅助线程中而提出的问题。我的问题是我在线程中设置了 抑制流动 ExecutionContext, 那么在后续的辅助线程中应该就不会有之前那个线程的ExecutionContext信息,就像上述程序中我在主线程中设置了禁止访问FileDialog的安全方面的设置,并在CallContext中添加了数据,如果我没有抑制ExecutionContext流动,那么就像你给的那些资料上写的,在该线程中调用的其它线程可以访问到该线程的ExecutionContext信息,那么就是在辅助线程中不能访问FileDialog,可以访问CallContext中设置的数据.但是我现在做了抑制ExecutionContext流动,那么在辅助线程中它不会受到之前那个发起调用的线程的ExecutionContext影响,它可以安全的访问FileDialog,但是它的CallContext中不会有之前线程中设置的数据。上述程序中ThreadPool调用后的结果是符合预期的。然后我使用了delegate异步方式来调用,它本质也是使用线程吃中的线程来执行,同样因为 抑制了ExecutionContext的流动,所以在执行异步的线程中也不会受到之前线程的ExecutionContext的影响,可以安全访问FileDialog, 这个输出结果也是符合预期的,但是问题出在它本来应该不能访问到之前发起调用线程(在上述的代码中就是主线程)中设置在CallContext中的数据,但是运行结果却是可以访问到,这个就是和预期不符,这也就是我想提出的问题。

     

    呵呵,说了一堆,不知道你能明白我的意思吗,如果你能知道缘由,也希望你能及时回复,再次感谢。


    AndersTan
    2011年10月11日 17:19
  • mazhou, 你有问过 CLR Team吗?盼回复

    AndersTan
    2011年10月13日 5:16
  • 不好意思,今天才看到帖子的回复。

    回答帖子以后,我自己也做了一些 Demo,wenjin.xu 也是我邀请来这里回答问题提供参考的。我分别利用 Thread, ThreadPool, Delegate, Task, SynchronizationContext, 以及 await/async 的方法试过 (Windows Forms),结果发现,只有 Delegate 能够拿到 LogicalData。

    Delegate 是在 ThreadPool 上跑肯定没错,不过至于为什么它可以拿到 Data 我也不得而知。我会把帖子发给相应的 Team 或者 DL 看看有没有什么好的解释方法。

    再次感谢您对 MSDN 论坛的参与。


    Mark Zhou
    2011年10月13日 9:06
  • 我已经收到了从 CLR Team 的回复。他是这么说的:

    This surprised me as well, so I had a quick look at the code.  Async delegate invocation is implemented using some Remoting infrastructure, which turns out to capture and restore the logical CallContext explicitly, regardless of whether the overall ExecutionContext flow is suppressed.  I’m not sure what the rationale for this is, but at this point we have to call it a “feature” because changing it would break any code that relies on the behavior.

    BTW, that remoting infrastructure is actually quite expensive.  For that reason, I usually recommend avoiding async delegate invocation where possible, independent of this issue.  As of .NET 4, the Task type provides a much more efficient way to execute code asynchronously; prior to .NET 4, QueueUserWorkItem may be your best bet.

    大概就是说,这东西他也没想到有这个结果,可能就是个 Bug,异步的 Delegate 是利用 Remoting 架构实现的,里面有一些对 LogicData 的特殊处理,而不管是否改变/抑制了 Control Flow。然后建议是,尽量使用 Task 或者 ThreadPool 来处理异步,避免直接调用异步的 Delegate。

    希望这个回答对您有帮助。


    Mark Zhou
    • 已标记为答案 Anders Tan 2011年10月18日 13:17
    2011年10月18日 10:39
  • 再次感谢你的官方回复,也让我们知道了原来委托异步是利用 Remoting 架构实现的 :)


    AndersTan
    2011年10月18日 13:20