寻找自定义 SynchronizationContext 的示例(单元测试所需)

Posted

技术标签:

【中文标题】寻找自定义 SynchronizationContext 的示例(单元测试所需)【英文标题】:Looking for an example of a custom SynchronizationContext (Required for unit testing) 【发布时间】:2021-05-24 09:48:07 【问题描述】:

我需要一个自定义的SynchronizationContext:

拥有一个运行“Posts”和“Sends”委托的线程 是否按照发送顺序发送 不需要其他方法

我需要这个,所以我可以对一些线程代码进行单元测试,这些代码将在实际应用程序中与 WinForm 对话。

在我自己写之前,我希望有人能指出我一个简单(和小)的实现。

【问题讨论】:

【参考方案1】:

这是我前段时间写的,没有版权问题,也没有任何保证(系统没有投产):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Threading;

namespace ManagedHelpers.Threads

    public class STASynchronizationContext : SynchronizationContext, IDisposable
    
        private readonly Dispatcher dispatcher;
        private object dispObj;
        private readonly Thread mainThread;

        public STASynchronizationContext()
        
            mainThread = new Thread(MainThread)  Name = "STASynchronizationContextMainThread", IsBackground = false ;
            mainThread.SetApartmentState(ApartmentState.STA);
            mainThread.Start();

            //wait to get the main thread's dispatcher
            while (Thread.VolatileRead(ref dispObj) == null)
                Thread.Yield();

            dispatcher = dispObj as Dispatcher;
        

        public override void Post(SendOrPostCallback d, object state)
        
            dispatcher.BeginInvoke(d, new object[]  state );
        

        public override void Send(SendOrPostCallback d, object state)
        
            dispatcher.Invoke(d, new object[]  state );
        

        private void MainThread(object param)
        
            Thread.VolatileWrite(ref dispObj, Dispatcher.CurrentDispatcher);
            Console.WriteLine("Main Thread is setup ! Id = 0", Thread.CurrentThread.ManagedThreadId);
            Dispatcher.Run();
        

        public void Dispose()
        
            if (!dispatcher.HasShutdownStarted && !dispatcher.HasShutdownFinished)
                dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);

            GC.SuppressFinalize(this);
        

        ~STASynchronizationContext()
        
            Dispose();
        
    

【讨论】:

这看起来不错,又漂亮又简单。在询问近 3 年后,它真的被回答并接受了吗?!我打算试试这个,但是“它有用吗?” @JonathonReinhart - 是的,它在我的开发测试期间确实有效,如果您发现任何问题,请告诉我,以便我们改进答案。【参考方案2】:

idesign.net(在页面上搜索自定义同步上下文)有一个 SynchronizationContext 可以完成这项工作,但我需要它更复杂。

【讨论】:

链接已经烂透了。您能否包含一些用于创建自定义同步上下文的相关代码? 代码见idesign.net/Downloads,对不起,我不知道版权是否允许我把它放在答案中。【参考方案3】:

有一个类似的要求 - 对服务器组件进行单元测试,以确认它的回调委托调用已编组到适当的 SynchronizationContext 并提出以下代码(基于 Stephen Toub 的博客文章 http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx),我认为它更简单,并且更通用,因为它使用自己的内部线程来服务 Post()/Send() 请求,而不是依赖 WPF/Winforms/.. 来执行调度。

    // A simple SynchronizationContext that encapsulates it's own dedicated task queue and processing
    // thread for servicing Send() & Post() calls.  
    // Based upon http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx but uses it's own thread
    // rather than running on the thread that it's instanciated on
    public sealed class DedicatedThreadSynchronisationContext : SynchronizationContext, IDisposable
    
        public DedicatedThreadSynchronisationContext()
        
            m_thread = new Thread(ThreadWorkerDelegate);
            m_thread.Start(this);
        

        public void Dispose()
        
            m_queue.CompleteAdding();
        

        /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
        /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
        /// <param name="state">The object passed to the delegate.</param>
        public override void Post(SendOrPostCallback d, object state)
        
            if (d == null) throw new ArgumentNullException("d");
            m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
        

        /// <summary> As 
        public override void Send(SendOrPostCallback d, object state)
        
            using (var handledEvent = new ManualResetEvent(false))
            
                Post(SendOrPostCallback_BlockingWrapper, Tuple.Create(d, state, handledEvent));
                handledEvent.WaitOne();
            
        

        public int WorkerThreadId  get  return m_thread.ManagedThreadId;  
        //=========================================================================================

        private static void SendOrPostCallback_BlockingWrapper(object state)
        
            var innerCallback = (state as Tuple<SendOrPostCallback, object, ManualResetEvent>);
            try
            
                innerCallback.Item1(innerCallback.Item2);
            
            finally
            
                innerCallback.Item3.Set();
            
        

        /// <summary>The queue of work items.</summary>
        private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
            new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();

        private readonly Thread m_thread = null;

        /// <summary>Runs an loop to process all queued work items.</summary>
        private void ThreadWorkerDelegate(object obj)
        
            SynchronizationContext.SetSynchronizationContext(obj as SynchronizationContext);

            try
            
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            
            catch (ObjectDisposedException)  
        
       

【讨论】:

Stephen Toub 提到的博文的当前地址是devblogs.microsoft.com/pfxteam/…【参考方案4】:

一些现代单元测试库(例如 xUnit)已经默认包含单线程 SynchronizationContext 实例。其他人没有(例如,MSTest)。

我的AsyncEx library 有一个AsyncContext,它安装了一个单线程SynchronizationContext,并且只处理该单线程上的消息队列。用法很简单:

public Task MyTestMethod() => AsyncContext.Run(async () =>

  // asynchronous code here.
);

AsyncContext 专为单元测试和控制台应用程序而设计,并且可以正确处理一些更奇特的场景,例如:

async void 方法被检测到,Run 方法在它们完成之前不会返回。 AsyncContext.Run 允许返回结果值,如果您想在异步代码之外进行断言,这很有用。 如果传递给Run 的委托传播异常如果任何异常传播出async void 方法,则该异常传播出AsyncContext.Run(没有异常包装器和保留异常调用堆栈)。

但是,AsyncContext只是一个SynchronizationContext(带有“运行器”),并且没有特定于 UI 的线程同步机制的概念(例如,DispatcherControl.Invoke )。如果您需要测试使用调度程序或控件的代码,则需要使用 WpfContext or WindowsFormsContext,它们是原始 Async CTP 中的辅助类型。

【讨论】:

【参考方案5】:

我已经修改了Bond 的答案,以消除对 WPF (Dispatcher) 的依赖,转而依赖 WinForms:

namespace ManagedHelpers.Threads
   
   using System;
   using System.Collections.Generic;
   using System.Diagnostics;
   using System.Linq;
   using System.Text;
   using System.Threading;
   using System.Threading.Tasks;
   using System.Windows.Forms;
   using NUnit.Framework;

   public class STASynchronizationContext : SynchronizationContext, IDisposable
      
      private readonly Control control;
      private readonly int mainThreadId;

      public STASynchronizationContext()
         
         this.control = new Control();

         this.control.CreateControl();

         this.mainThreadId = Thread.CurrentThread.ManagedThreadId;

         if (Thread.CurrentThread.Name == null)
            
            Thread.CurrentThread.Name = "AsynchronousTestRunner Main Thread";
            
         

      public override void Post(SendOrPostCallback d, object state)
         
         control.BeginInvoke(d, new object[]  state );
         

      public override void Send(SendOrPostCallback d, object state)
         
         control.Invoke(d, new object[]  state );
         

      public void Dispose()
         
         Assert.AreEqual(this.mainThreadId, Thread.CurrentThread.ManagedThreadId);

         this.Dispose(true);
         GC.SuppressFinalize(this);
         

      protected virtual void Dispose(bool disposing)
         
         Assert.AreEqual(this.mainThreadId, Thread.CurrentThread.ManagedThreadId);

         if (disposing)
            
            if (control != null)
               
               control.Dispose();
               
            
         

      ~STASynchronizationContext()
         
         this.Dispose(false);
         
      
   

【讨论】:

这基本上是对WinFormsSynchronizationContext 的重新实现,其作用基本相同。

以上是关于寻找自定义 SynchronizationContext 的示例(单元测试所需)的主要内容,如果未能解决你的问题,请参考以下文章

Spring Eureka 寻找默认端口 8761 而不是自定义端口

自定义寻找bug简单框架 (还没完成)

python编写自定义函数计算约登值(约登指数Youden Index)寻找最佳阈值(thresholdcutoff)以及最佳阈值对应的ROC曲线中的坐标点

自定义 UpdateSourceTrigger 延迟?

iPhone - NavigationBar 自定义背景 [重复]

在 Swift 中测试自定义视图