diff --git a/FolderSyncNet.csproj b/FolderSyncNet.csproj index 45125ec..000ddd9 100644 --- a/FolderSyncNet.csproj +++ b/FolderSyncNet.csproj @@ -77,6 +77,9 @@ packages\AsyncEnumerator.4.0.2\lib\net461\AsyncEnumerable.dll + + packages\AsyncKeyedLock.7.1.3\lib\netstandard2.0\AsyncKeyedLock.dll + packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll @@ -396,4 +399,4 @@ - \ No newline at end of file + diff --git a/InitialScan.cs b/InitialScan.cs index 24f6560..8394aef 100644 --- a/InitialScan.cs +++ b/InitialScan.cs @@ -242,7 +242,7 @@ private static IAsyncEnumerable ProcessSubDirs(FolderSyncNetSource.Dir var historyFileInfosDict = new Dictionary(); //var historyFileInfosTask = Task.CompletedTask; - AsyncLockQueueDictionary.LockDictReleaser destOrHistoryDirCacheLock = null; + IDisposable destOrHistoryDirCacheLock = null; //AsyncLockQueueDictionary.LockDictReleaser historyDirCacheLock = null; FileInfo[] fileInfos = null; diff --git a/Synchronisation.cs b/Synchronisation.cs index 864c39a..831594b 100644 --- a/Synchronisation.cs +++ b/Synchronisation.cs @@ -7,141 +7,39 @@ // #define ASYNC +using AsyncKeyedLock; using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Nito.AsyncEx; namespace FolderSync { public class AsyncLockQueueDictionary where KeyT : IComparable, IEquatable { - private static readonly bool IsStringDictionary = typeof(KeyT) == typeof(string); - - private readonly object DictionaryAccessMutex = new object(); - //private readonly SemaphoreSlim DictionaryAccessMutex = new SemaphoreSlim(1, 1); - private readonly Dictionary LockQueueDictionary = new Dictionary(); - - public sealed class AsyncLockWithWaiterCount - { - public readonly AsyncLock LockEntry; -#pragma warning disable S1104 //Warning S1104 Make this field 'private' and encapsulate it in a 'public' property. - public int WaiterCount; -#pragma warning restore S1104 - - public AsyncLockWithWaiterCount() - { - this.LockEntry = new AsyncLock(); - this.WaiterCount = 1; - } - } + private readonly AsyncKeyedLocker LockQueueDictionary = new AsyncKeyedLocker(); - public sealed class LockDictReleaser : IDisposable //TODO: implement IAsyncDisposable in .NET 5.0 - { - private readonly KeyT Name; - private readonly AsyncLockWithWaiterCount LockEntry; -#if !NOASYNC - private readonly IDisposable LockHandle; -#endif - private readonly AsyncLockQueueDictionary AsyncLockQueueDictionary; - -#if NOASYNC - internal LockDictReleaser(KeyT name, AsyncLockWithWaiterCount lockEntry, AsyncLockQueueDictionary asyncLockQueueDictionary) -#else - internal LockDictReleaser(KeyT name, AsyncLockWithWaiterCount lockEntry, IDisposable lockHandle, AsyncLockQueueDictionary asyncLockQueueDictionary) -#endif - { - this.Name = name; - this.LockEntry = lockEntry; -#if !NOASYNC - this.LockHandle = lockHandle; -#endif - this.AsyncLockQueueDictionary = asyncLockQueueDictionary; - } - - public void Dispose() - { -#if NOASYNC - this.AsyncLockQueueDictionary.ReleaseLock(this.Name, this.LockEntry); -#else - this.AsyncLockQueueDictionary.ReleaseLock(this.Name, this.LockEntry, this.LockHandle); -#endif - } - } + private static readonly bool IsStringDictionary = typeof(KeyT) == typeof(string); -#if NOASYNC - private void ReleaseLock(KeyT name, AsyncLockWithWaiterCount lockEntry) -#else - private void ReleaseLock(KeyT name, AsyncLockWithWaiterCount lockEntry, IDisposable lockHandle) -#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async ValueTask LockAsync(KeyT name) { -#if NOASYNC - Monitor.Exit(lockEntry.LockEntry); -#else - lockHandle.Dispose(); -#endif - - lock (DictionaryAccessMutex) - //DictionaryAccessMutex.Wait(); - //try - { - lockEntry.WaiterCount--; - Debug.Assert(lockEntry.WaiterCount >= 0); - - if (lockEntry.WaiterCount == 0) //NB! - { - LockQueueDictionary.Remove(name); - } - } - //finally - //{ - // DictionaryAccessMutex.Release(); - //} + return await LockQueueDictionary.LockAsync(name).ConfigureAwait(false); } - public async Task LockAsync(KeyT name, CancellationToken cancellationToken = default(CancellationToken)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async ValueTask LockAsync(KeyT name, CancellationToken cancellationToken) { - AsyncLockWithWaiterCount lockEntry; -#pragma warning disable PH_S023 //Message PH_S023 Using the blocking synchronization mechanics of a monitor inside an async method is discouraged; use SemaphoreSlim instead. - lock (DictionaryAccessMutex) -#pragma warning restore PH_S023 - //await DictionaryAccessMutex.WaitAsync(cancellationToken); - //try - { - if (!LockQueueDictionary.TryGetValue(name, out lockEntry)) - { - lockEntry = new AsyncLockWithWaiterCount(); - LockQueueDictionary.Add(name, lockEntry); - } - else - { - lockEntry.WaiterCount++; //NB! must be done inside the lock and BEFORE waiting for the lock - } - } - //finally - //{ - // DictionaryAccessMutex.Release(); - //} - -#if NOASYNC -#pragma warning disable PH_P006 //warning PH_P006 Favor the use of the lock-statement instead of the use of Monitor.Enter when no timeouts are needed. - Monitor.Enter(lockEntry.LockEntry); -#pragma warning restore PH_P006 - return new LockDictReleaser(name, lockEntry, this); -#else - var lockHandle = await lockEntry.LockEntry.LockAsync(cancellationToken); - return new LockDictReleaser(name, lockEntry, lockHandle, this); -#endif + return await LockQueueDictionary.LockAsync(name, cancellationToken).ConfigureAwait(false); } public sealed class MultiLockDictReleaser : IDisposable //TODO: implement IAsyncDisposable in .NET 5.0 { - private readonly LockDictReleaser[] Releasers; + private readonly IDisposable[] Releasers; - public MultiLockDictReleaser(params LockDictReleaser[] releasers) + public MultiLockDictReleaser(params IDisposable[] releasers) { this.Releasers = releasers; } @@ -156,6 +54,7 @@ public void Dispose() } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public async Task LockAsync(KeyT name1, KeyT name2, CancellationToken cancellationToken = default(CancellationToken)) { var names = new List() diff --git a/packages.config b/packages.config index 9f9985a..47dfd9e 100644 --- a/packages.config +++ b/packages.config @@ -4,6 +4,7 @@ +