AsyncLock.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Collections.Generic;
  5. namespace System.Reactive.Concurrency
  6. {
  7. /// <summary>
  8. /// Asynchronous lock.
  9. /// </summary>
  10. public sealed class AsyncLock : IDisposable
  11. {
  12. private object guard = new object();
  13. private Queue<Action> queue;
  14. private bool isAcquired = false;
  15. private bool hasFaulted = false;
  16. /// <summary>
  17. /// Queues the action for execution. If the caller acquires the lock and becomes the owner,
  18. /// the queue is processed. If the lock is already owned, the action is queued and will get
  19. /// processed by the owner.
  20. /// </summary>
  21. /// <param name="action">Action to queue for execution.</param>
  22. /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
  23. public void Wait(Action action)
  24. {
  25. if (action == null)
  26. throw new ArgumentNullException(nameof(action));
  27. // allow one thread to update the state
  28. lock (guard)
  29. {
  30. // if a previous action crashed, ignore any future actions
  31. if (hasFaulted)
  32. {
  33. return;
  34. }
  35. // if the "lock" is busy, queue up the extra work
  36. // otherwise there is no need to queue up "action"
  37. if (isAcquired)
  38. {
  39. // create the queue if necessary
  40. var q = queue;
  41. if (q == null)
  42. {
  43. q = new Queue<Action>();
  44. queue = q;
  45. }
  46. // enqueue the work
  47. q.Enqueue(action);
  48. return;
  49. }
  50. // indicate there is processing going on
  51. isAcquired = true;
  52. }
  53. // if we get here, execute the "action" first
  54. for (; ; )
  55. {
  56. try
  57. {
  58. action();
  59. }
  60. catch
  61. {
  62. // the execution failed, terminate this AsyncLock
  63. lock (guard)
  64. {
  65. // throw away the queue
  66. queue = null;
  67. // report fault
  68. hasFaulted = true;
  69. }
  70. throw;
  71. }
  72. // execution succeeded, let's see if more work has to be done
  73. lock (guard)
  74. {
  75. var q = queue;
  76. // either there is no queue yet or we run out of work
  77. if (q == null || q.Count == 0)
  78. {
  79. // release the lock
  80. isAcquired = false;
  81. return;
  82. }
  83. // get the next work action
  84. action = q.Dequeue();
  85. }
  86. // loop back and execute the action
  87. }
  88. }
  89. /// <summary>
  90. /// Clears the work items in the queue and drops further work being queued.
  91. /// </summary>
  92. public void Dispose()
  93. {
  94. lock (guard)
  95. {
  96. queue = null;
  97. hasFaulted = true;
  98. }
  99. }
  100. }
  101. }