SingleThreadedTestSynchronizationContext
using NUnit.Framework.Interfaces;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace NUnit.Framework.Internal
{
[NullableContext(1)]
[Nullable(0)]
internal sealed class SingleThreadedTestSynchronizationContext : SynchronizationContext, IDisposable
{
[NullableContext(0)]
private enum Status
{
NotStarted,
Running,
ShuttingDown,
ShutDown
}
[NullableContext(2)]
[Nullable(0)]
private struct ScheduledWork
{
[Nullable(1)]
private readonly SendOrPostCallback _callback;
private readonly object _state;
private readonly ManualResetEventSlim _finished;
public ScheduledWork([Nullable(1)] SendOrPostCallback callback, object state, ManualResetEventSlim finished)
{
_callback = callback;
_state = state;
_finished = finished;
}
public void Execute()
{
_callback(_state);
_finished?.Set();
}
}
private const string ShutdownTimeoutMessage = "Work posted to the synchronization context did not complete within ten seconds. Consider explicitly waiting for the work to complete.";
private readonly TimeSpan _shutdownTimeout;
private readonly Queue<ScheduledWork> _queue = new Queue<ScheduledWork>();
private Status _status;
[Nullable(2)]
private Stopwatch _timeSinceShutdown;
public SingleThreadedTestSynchronizationContext(TimeSpan shutdownTimeout)
{
_shutdownTimeout = shutdownTimeout;
}
public override void Post(SendOrPostCallback d, [Nullable(2)] object state)
{
Guard.ArgumentNotNull(d, "d");
AddWork(new ScheduledWork(d, state, null));
}
public override void Send(SendOrPostCallback d, [Nullable(2)] object state)
{
Guard.ArgumentNotNull(d, "d");
if (SynchronizationContext.Current == this)
d(state);
else {
using (ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim()) {
AddWork(new ScheduledWork(d, state, manualResetEventSlim));
manualResetEventSlim.Wait();
}
}
}
private void AddWork(ScheduledWork work)
{
lock (_queue) {
switch (_status) {
case Status.ShuttingDown:
if (_timeSinceShutdown.Elapsed < _shutdownTimeout)
break;
goto case Status.ShutDown;
case Status.ShutDown:
throw ErrorAndGetExceptionForShutdownTimeout();
}
_queue.Enqueue(work);
Monitor.Pulse(_queue);
}
}
public void ShutDown()
{
lock (_queue) {
Status status = _status;
if ((uint)(status - 2) > 1) {
_timeSinceShutdown = Stopwatch.StartNew();
_status = Status.ShuttingDown;
Monitor.Pulse(_queue);
}
}
}
public void Run()
{
lock (_queue) {
Status status = _status;
if (status == Status.Running)
throw new InvalidOperationException("SingleThreadedTestSynchronizationContext.Run may not be reentered.");
if ((uint)(status - 2) <= 1)
throw new InvalidOperationException("This SingleThreadedTestSynchronizationContext has been shut down.");
_status = Status.Running;
}
ScheduledWork scheduledWork;
while (TryTake(out scheduledWork)) {
scheduledWork.Execute();
}
}
private bool TryTake(out ScheduledWork scheduledWork)
{
lock (_queue) {
while (_queue.Count == 0) {
if (_status == Status.ShuttingDown) {
_status = Status.ShutDown;
scheduledWork = default(ScheduledWork);
return false;
}
Monitor.Wait(_queue);
}
if (_status == Status.ShuttingDown && _timeSinceShutdown.Elapsed > _shutdownTimeout) {
_status = Status.ShutDown;
throw ErrorAndGetExceptionForShutdownTimeout();
}
scheduledWork = _queue.Dequeue();
}
return true;
}
private static Exception ErrorAndGetExceptionForShutdownTimeout()
{
TestExecutionContext.CurrentContext?.CurrentResult.RecordAssertion(AssertionStatus.Error, "Work posted to the synchronization context did not complete within ten seconds. Consider explicitly waiting for the work to complete.");
return new InvalidOperationException("Work posted to the synchronization context did not complete within ten seconds. Consider explicitly waiting for the work to complete.");
}
public void Dispose()
{
ShutDown();
}
}
}