Skip to content

Instantly share code, notes, and snippets.

@rorymacleod
Last active December 10, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rorymacleod/4445306 to your computer and use it in GitHub Desktop.
Save rorymacleod/4445306 to your computer and use it in GitHub Desktop.
A derived SynchronizationContext for use in unit tests. There's no easy way to truly post a callback onto the same thread that's running the test, as if it was the UI thread, but this code at least allows the callback to identify the SynchronizationContext it was posted to.
/// <summary>
/// Provides a synchronization context for unit tests.
/// </summary>
public sealed class TestSynchronizationContext: SynchronizationContext
{
/// <summary>
/// A reference to the <c>TestSynchronizationContext</c> that has posted or sent a delegate to the current
/// thread.
/// </summary>
[ThreadStatic]
private static SynchronizationContext ThreadContext;
/// <summary>
/// Gets a value indicating whether the current thread is executing a delegate that was posted or sent to the
/// current context.
/// </summary>
public bool ExecutingInContext
{
get
{
return ThreadContext == this;
}
}
/// <summary>
/// <c>True</c> if posted delegates should be executed synchronously, or <c>False</c> to use the thread pool.
/// </summary>
private readonly bool Synchronous;
/// <summary>
/// Occurs when the context has finished executing a delegate.
/// </summary>
public event EventHandler ExecutionComplete;
/// <summary>
/// Initializes a new instance of the <see cref="TestSynchronizationContext"/> class.
/// </summary>
public TestSynchronizationContext() : this(false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TestSynchronizationContext"/> class.
/// </summary>
/// <param name="synchronous"><c>True</c> if posted delegates should be executed synchronously, or <c>False</c>
/// to use the thread pool.</param>
public TestSynchronizationContext(bool synchronous)
{
Synchronous = synchronous;
}
/// <summary>
/// Raises the <see cref="ExecutionComplete"/> event.
/// </summary>
private void OnExecutionComplete()
{
if (ExecutionComplete != null)
{
ExecutionComplete(this, EventArgs.Empty);
}
}
/// <summary>
/// Dispatches an asynchronous message to a synchronization context.
/// </summary>
/// <param name="d">The <see cref="SendOrPostCallback"/> delegate to call.</param>
/// <param name="state">The object passed to the delegate.</param>
public override void Post(SendOrPostCallback d, object state)
{
if (Synchronous)
{
Send(d, state);
}
else
{
ThreadPool.QueueUserWorkItem(o => Send(d, o), state);
}
}
/// <summary>
/// Dispatches a synchronous message to a synchronization context.
/// </summary>
/// <param name="d">The <see cref="SendOrPostCallback"/> delegate to call.</param>
/// <param name="state">The object passed to the delegate.</param>
public override void Send(SendOrPostCallback d, object state)
{
var previousContext = Current;
try
{
SetSynchronizationContext(this);
ThreadContext = this;
d(state);
}
finally
{
SetSynchronizationContext(previousContext);
ThreadContext = previousContext is TestSynchronizationContext ? previousContext : null;
OnExecutionComplete();
}
}
}
@rorymacleod
Copy link
Author

For revision 02bd0ff, this cleaner version gets rid of the token because a reference to the context does the same thing. Also:

  • You can now check the ExecutingInContext property to tell whether the executing code was posted or sent to the context.
  • You have the option of making posts run synchronously or on the thread pool.
  • I replaced the wait handle with an event where a test can use its own wait handle, or do things like check the thread ID.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment