diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs index 0a9c79a80e..b4e8e64312 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/AsyncLock.cs @@ -63,14 +63,14 @@ namespace Umbraco.Core // for anonymous semaphore, use the unique releaser, else create a new one return _semaphore != null ? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore) - : (IDisposable)new NamedSemaphoreReleaser(_semaphore2); + : new NamedSemaphoreReleaser(_semaphore2); } public Task LockAsync() { var wait = _semaphore != null - ? _semaphore.WaitAsync() - : WaitOneAsync(_semaphore2); + ? _semaphore.WaitAsync() + : _semaphore2.WaitOneAsync(); return wait.IsCompleted ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named @@ -79,6 +79,19 @@ namespace Umbraco.Core TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } + public Task LockAsync(int millisecondsTimeout) + { + var wait = _semaphore != null + ? _semaphore.WaitAsync(millisecondsTimeout) + : _semaphore2.WaitOneAsync(millisecondsTimeout); + + return wait.IsCompleted + ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named + : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), + this, CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + public IDisposable Lock() { if (_semaphore != null) @@ -168,40 +181,5 @@ namespace Umbraco.Core Dispose(false); } } - - // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 - // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html - // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... - // version below should be OK - - private static Task WaitOneAsync(WaitHandle handle) - { - var tcs = new TaskCompletionSource(); - var callbackHandleInitLock = new object(); - lock (callbackHandleInitLock) - { - RegisteredWaitHandle callbackHandle = null; - // ReSharper disable once RedundantAssignment - callbackHandle = ThreadPool.RegisterWaitForSingleObject( - handle, - (state, timedOut) => - { - tcs.SetResult(null); - - // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. - lock (callbackHandleInitLock) - { - // ReSharper disable once PossibleNullReferenceException - // ReSharper disable once AccessToModifiedClosure - callbackHandle.Unregister(null); - } - }, - /*state:*/ null, - /*millisecondsTimeOutInterval:*/ Timeout.Infinite, - /*executeOnlyOnce:*/ true); - } - - return tcs.Task; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4e155e1b3d..5d5bb9807f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1215,6 +1215,7 @@ + diff --git a/src/Umbraco.Core/WaitHandleExtensions.cs b/src/Umbraco.Core/WaitHandleExtensions.cs new file mode 100644 index 0000000000..0d840a2496 --- /dev/null +++ b/src/Umbraco.Core/WaitHandleExtensions.cs @@ -0,0 +1,44 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Core +{ + internal static class WaitHandleExtensions + { + + // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 + // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html + // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... + // version below should be OK + + public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout = Timeout.Infinite) + { + var tcs = new TaskCompletionSource(); + var callbackHandleInitLock = new object(); + lock (callbackHandleInitLock) + { + RegisteredWaitHandle callbackHandle = null; + // ReSharper disable once RedundantAssignment + callbackHandle = ThreadPool.RegisterWaitForSingleObject( + handle, + (state, timedOut) => + { + tcs.SetResult(null); + + // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. + lock (callbackHandleInitLock) + { + // ReSharper disable once PossibleNullReferenceException + // ReSharper disable once AccessToModifiedClosure + callbackHandle.Unregister(null); + } + }, + /*state:*/ null, + /*millisecondsTimeOutInterval:*/ millisecondsTimeout, + /*executeOnlyOnce:*/ true); + } + + return tcs.Task; + } + } +}