Alla ricerca di un esempio di un SynchronizationContext personalizzato (richiesto per il test dell’unità)

Ho bisogno di un SynchronizationContext personalizzato che:

  • Possiede un singolo thread che esegue delegati “Posts” e “Sends”
  • L’invio nell’ordine in cui sono inviati
  • Non sono necessari altri metodi

Ho bisogno di questo in modo da poter testare alcuni codice di threading che parlerà a WinForm nella vera applicazione.

Prima di scrivere il mio, speravo che qualcuno potesse indicarmi una semplice (e piccola) implementazione.

Questo è stato scritto da me qualche tempo fa, nessun problema con il copyright, nessuna garanzia neanche (il sistema non è andato in produzione):

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(); } } } 

idesign.net (ricerca di Custom Synchronization Context nella pagina) ha un SynchronizationContext che farà il lavoro, tuttavia è più complesso di cui ho bisogno.

Aveva un requisito simile: l’unità che testava un componente server per confermare che si trattava di invocazioni di delega di callback veniva eseguita su un SynchronizationContext appropriato e otteneva il seguente codice (basato sul post del blog di Stephen Toub http://blogs.msdn.com/b/pfxteam /archive/2012/01/20/10259049.aspx ) che ricono è più semplice e più generale in quanto utilizza il proprio thread interno per soddisfare le richieste Post() / Send() , piuttosto che affidarsi a WPF / Winforms / .. per eseguire il dispacciamento.

  // 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(); } /// Dispatches an asynchronous message to the synchronization context. /// The System.Threading.SendOrPostCallback delegate to call. /// The object passed to the delegate. public override void Post(SendOrPostCallback d, object state) { if (d == null) throw new ArgumentNullException("d"); m_queue.Add(new KeyValuePair(d, state)); } ///  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); try { innerCallback.Item1(innerCallback.Item2); } finally { innerCallback.Item3.Set(); } } /// The queue of work items. private readonly BlockingCollection> m_queue = new BlockingCollection>(); private readonly Thread m_thread = null; /// Runs an loop to process all queued work items. private void ThreadWorkerDelegate(object obj) { SynchronizationContext.SetSynchronizationContext(obj as SynchronizationContext); try { foreach (var workItem in m_queue.GetConsumingEnumerable()) workItem.Key(workItem.Value); } catch (ObjectDisposedException) { } } } 

Ho adattato la risposta di Bond per rimuovere la dipendenza da WPF (Dispatcher) e, invece, dipendo da 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); } } }