我需要一个自定义SynchronizationContext:
我需要这个,所以我可以单独测试一些线程代码,这些代码将在真实应用程序中与WinForm对话。
在我自己编写之前,我希望有人能指出我的简单(和小)实现。
答案 0 :(得分:10)
这个是我前段时间写的,没有版权问题,也没有保证(系统没有投入生产):
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();
}
}
}
答案 1 :(得分:8)
idesign.net(在页面上搜索自定义同步上下文)有一个可以完成工作的SynchronizationContext,但是我需要它们更复杂。
答案 2 :(得分:2)
有一个类似的要求 - 单元测试服务器组件以确认它的回调委托调用被编组到适当的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) { }
}
}
答案 3 :(得分:1)
一些现代单元测试库(例如 xUnit)默认已经包含单线程 void Update()
{
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 direction = mousePosition - transform.position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0, 0, angle);
// Debug.Log(angle);
}
实例。其他人没有(例如,MSTest)。
我的 AsyncEx library 有一个 SynchronizationContext
,它安装了一个单线程 AsyncContext
并且只处理该单线程上的消息队列。用法很简单:
SynchronizationContext
public Task MyTestMethod() => AsyncContext.Run(async () =>
{
// asynchronous code here.
});
是为单元测试和控制台应用程序设计的,并且可以正确处理一些更奇特的场景,例如:
AsyncContext
方法被检测到,async void
方法在它们完成之前不会返回。Run
允许返回结果值,如果您想在异步代码之外进行断言,这将非常有用。AsyncContext.Run
的委托传播异常或如果任何异常传播出 Run
方法,则该异常传播出 async void
(没有异常包装器并保留异常调用堆栈)。然而,AsyncContext.Run
只是一个AsyncContext
(带有一个“runner”),并且没有特定于 UI 的线程同步机制的概念(例如,{{1 }}, SynchronizationContext
)。如果您需要测试使用调度程序或控件的代码,则需要使用 WpfContext
or WindowsFormsContext
,它们是原始 Async CTP 中的辅助类型。
答案 4 :(得分:0)
我已经通过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);
}
}
}