/Okra.Core.Tests/Helpers/SynchronizationHelper.cs

http://okra.codeplex.com · C# · 142 lines · 90 code · 21 blank · 31 comment · 14 complexity · 9dd2e7ae84973713a086fa94c2db1409 MD5 · raw file

  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. // NB: Based upon code from the Microsoft PFX team
  9. // See link http://blogs.msdn.com/b/pfxteam/archive/2012/02/02/10263555.aspx
  10. namespace Okra.Tests.Helpers
  11. {
  12. public static class SynchronizationHelper
  13. {
  14. /// <summary>Runs the specified asynchronous method.</summary>
  15. /// <param name="asyncMethod">The asynchronous method to execute.</param>
  16. public static void Run(Action asyncMethod)
  17. {
  18. if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");
  19. var prevCtx = SynchronizationContext.Current;
  20. try
  21. {
  22. // Establish the new context
  23. var syncCtx = new SingleThreadSynchronizationContext();
  24. SynchronizationContext.SetSynchronizationContext(syncCtx);
  25. // Invoke the function
  26. syncCtx.OperationStarted();
  27. asyncMethod();
  28. syncCtx.OperationCompleted();
  29. // Pump continuations and propagate any exceptions
  30. syncCtx.RunOnCurrentThread();
  31. }
  32. finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
  33. }
  34. /// <summary>Runs the specified asynchronous method.</summary>
  35. /// <param name="asyncMethod">The asynchronous method to execute.</param>
  36. public static void Run(Func<Task> asyncMethod)
  37. {
  38. if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");
  39. var prevCtx = SynchronizationContext.Current;
  40. try
  41. {
  42. // Establish the new context
  43. var syncCtx = new SingleThreadSynchronizationContext();
  44. SynchronizationContext.SetSynchronizationContext(syncCtx);
  45. // Invoke the function and alert the context to when it completes
  46. var t = asyncMethod();
  47. if (t == null) throw new InvalidOperationException("No task provided.");
  48. t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);
  49. // Pump continuations and propagate any exceptions
  50. syncCtx.RunOnCurrentThread();
  51. t.GetAwaiter().GetResult();
  52. }
  53. finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
  54. }
  55. /// <summary>Runs the specified asynchronous method.</summary>
  56. /// <param name="asyncMethod">The asynchronous method to execute.</param>
  57. public static T Run<T>(Func<Task<T>> asyncMethod)
  58. {
  59. if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");
  60. var prevCtx = SynchronizationContext.Current;
  61. try
  62. {
  63. // Establish the new context
  64. var syncCtx = new SingleThreadSynchronizationContext();
  65. SynchronizationContext.SetSynchronizationContext(syncCtx);
  66. // Invoke the function and alert the context to when it completes
  67. var t = asyncMethod();
  68. if (t == null) throw new InvalidOperationException("No task provided.");
  69. t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);
  70. // Pump continuations and propagate any exceptions
  71. syncCtx.RunOnCurrentThread();
  72. return t.GetAwaiter().GetResult();
  73. }
  74. finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
  75. }
  76. // *** Private Sub-classes ***
  77. /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
  78. private sealed class SingleThreadSynchronizationContext : SynchronizationContext
  79. {
  80. /// <summary>The queue of work items.</summary>
  81. private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
  82. new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
  83. /// <summary>The processing thread.</summary>
  84. //private readonly Thread m_thread = Thread.CurrentThread;
  85. /// <summary>The number of outstanding operations.</summary>
  86. private int m_operationCount = 0;
  87. /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
  88. /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
  89. /// <param name="state">The object passed to the delegate.</param>
  90. public override void Post(SendOrPostCallback d, object state)
  91. {
  92. if (d == null) throw new ArgumentNullException("d");
  93. m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
  94. }
  95. /// <summary>Not supported.</summary>
  96. public override void Send(SendOrPostCallback d, object state)
  97. {
  98. throw new NotSupportedException("Synchronously sending is not supported.");
  99. }
  100. /// <summary>Runs an loop to process all queued work items.</summary>
  101. public void RunOnCurrentThread()
  102. {
  103. foreach (var workItem in m_queue.GetConsumingEnumerable())
  104. workItem.Key(workItem.Value);
  105. }
  106. /// <summary>Notifies the context that no more work will arrive.</summary>
  107. public void Complete() { m_queue.CompleteAdding(); }
  108. /// <summary>Invoked when an async operation is started.</summary>
  109. public override void OperationStarted()
  110. {
  111. Interlocked.Increment(ref m_operationCount);
  112. }
  113. /// <summary>Invoked when an async operation is completed.</summary>
  114. public override void OperationCompleted()
  115. {
  116. if (Interlocked.Decrement(ref m_operationCount) == 0)
  117. Complete();
  118. }
  119. }
  120. }
  121. }