寻找自定义 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 的线程同步机制的概念(例如,Dispatcher
、Control.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 而不是自定义端口
python编写自定义函数计算约登值(约登指数Youden Index)寻找最佳阈值(thresholdcutoff)以及最佳阈值对应的ROC曲线中的坐标点