Why SynchronizationContext does not work properly?

后端 未结 3 1100
北海茫月
北海茫月 2020-12-19 21:38

I have following code:

[TestMethod]
public void StartWorkInFirstThread()
{
    if (SynchronizationContext.Current == null)
        SynchronizationContext.Set         


        
3条回答
  •  被撕碎了的回忆
    2020-12-19 22:23

    Your expectation is wrong because there's no general way to "inject" a delegate into a running thread. Your "first thread" was started in the test runner, will execute one or more tests, and will then stop - there's no way to interrupt it and tell it to run CallbackInFirstThread. The SynchronizationContext class runs Post-ed delegates in the thread pool because that's about the only option it has.

    Derived classes like WindowsFormsSynchronizationContext make use of the message loop in WinForms applications to pass the Post-ed delegate to the UI thread, but there's no equivalent in a test runner.

    If you want to check which SynchronizationContext the code you're testing is using, you could create your own derived class that sets a flag you can check in your test. Here's an example:

    public class TestSynchronizationContext : SynchronizationContext
    {
        [ThreadStatic]
        private static object _CurrentPostToken;
        /// 
        /// Gets the context's token, if the current thread is executing a delegate that
        /// was posted to this context; otherwise, null.
        /// 
        public static object CurrentPostToken
        {
            get
            {
                return _CurrentPostToken;
            }
        }
    
        public object Token { get; private set; }
    
        /// 
        /// Gets a WaitHandle that is set after the context executes a posted delegate.
        /// 
        public AutoResetEvent PostHandle { get; private set; }
    
        public TestSynchronizationContext(object token)
        {
            Token = token;
            PostHandle = new AutoResetEvent(false);
        }
    
        public override void Post(SendOrPostCallback d, object state)
        {
            try
            {
                _CurrentPostToken = Token;
                // Execute the callback on this thread, so that we can reset the context
                // when it's finished.
                d(state);
            }
            finally
            {
                _CurrentPostToken = null;
            }
    
            // The test method will wait on this handle so that it doesn't exit before
            // the synchronization context is called.
            PostHandle.Set();
        }
    }
    

    In StartWorkInFirstThread, set the context to an instance of TestSynchronizationContext:

    SynchronizationContext.SetSynchronizationContext(
            new TestSynchronizationContext(new object()));
    

    After you call BeginInvoke, you need to wait for the Post to happen before you exit the test, so call:

    ((TestSynchronizationContext)SynchronizationContext.Current).PostHandle.WaitOne(1000);
    

    In CallbackInFirstThread you can check what context is being used with something like:

    Assert.IsNotNull(TestSynchronizationContext.CurrentPostToken);
    

    The point is that there's no easy way to actually post back to the first thread, but you can check that the right context is being used so that, when your code runs in a real application, the callback will be running in the UI thread.

提交回复
热议问题