diff --git a/src/.editorconfig b/src/.editorconfig
index dbffb65a01b..c1cf5298594 100644
--- a/src/.editorconfig
+++ b/src/.editorconfig
@@ -5,6 +5,11 @@ root = false
# Code files
[*.cs]
+# Design rules
+
+# CA1001: Types that own disposable fields should be disposable
+dotnet_diagnostic.CA1001.severity = error
+
# Globalization rules
# CA1303: Do not pass literals as localized parameters
diff --git a/src/EFCore/Internal/AsyncLock.cs b/src/EFCore/Internal/AsyncLock.cs
deleted file mode 100644
index 74dd39964e6..00000000000
--- a/src/EFCore/Internal/AsyncLock.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using JetBrains.Annotations;
-
-namespace Microsoft.EntityFrameworkCore.Internal
-{
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public sealed class AsyncLock
- {
- private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
- private readonly Releaser _releaser;
- private readonly Task _releaserTask;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public AsyncLock()
- {
- _releaser = new Releaser(this);
- _releaserTask = Task.FromResult(_releaser);
- }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public Task LockAsync(CancellationToken cancellationToken = default)
- {
- var wait = _semaphore.WaitAsync(cancellationToken);
-
- return wait.IsCompleted
- ? _releaserTask
- : wait.ContinueWith(
- (_, state) => ((AsyncLock)state)._releaser,
- this, CancellationToken.None,
- TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
- }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public Releaser Lock()
- {
- _semaphore.Wait();
-
- return _releaser;
- }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public readonly struct Releaser : IDisposable
- {
- private readonly AsyncLock _toRelease;
-
- internal Releaser([NotNull] AsyncLock toRelease)
- {
- _toRelease = toRelease;
- }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public void Dispose()
- => _toRelease._semaphore.Release();
- }
- }
-}
diff --git a/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs b/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs
index 9492508e9a0..86752c94740 100644
--- a/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs
+++ b/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs
@@ -15,9 +15,9 @@ namespace Microsoft.EntityFrameworkCore.ValueGeneration
///
/// The thread safe state used by .
///
- public class HiLoValueGeneratorState
+ public class HiLoValueGeneratorState : IDisposable
{
- private readonly AsyncLock _asyncLock = new AsyncLock();
+ private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
private HiLoValue _currentValue;
private readonly int _blockSize;
@@ -58,7 +58,8 @@ public virtual TValue Next([NotNull] Func getNewLowValue)
// gets a chance to use the new value, so use a while here to do it all again.
while (newValue.Low >= newValue.High)
{
- using (_asyncLock.Lock())
+ _semaphoreSlim.Wait();
+ try
{
// Once inside the lock check to see if another thread already got a new block, in which
// case just get a value out of the new block instead of requesting one.
@@ -73,6 +74,10 @@ public virtual TValue Next([NotNull] Func getNewLowValue)
newValue = GetNextValue();
}
}
+ finally
+ {
+ _semaphoreSlim.Release();
+ }
}
return ConvertResult(newValue);
@@ -100,7 +105,8 @@ public virtual async ValueTask NextAsync(
// gets a chance to use the new value, so use a while here to do it all again.
while (newValue.Low >= newValue.High)
{
- using (await _asyncLock.LockAsync(cancellationToken).ConfigureAwait(false))
+ await _semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
+ try
{
// Once inside the lock check to see if another thread already got a new block, in which
// case just get a value out of the new block instead of requesting one.
@@ -115,6 +121,10 @@ public virtual async ValueTask NextAsync(
newValue = GetNextValue();
}
}
+ finally
+ {
+ _semaphoreSlim.Release();
+ }
}
return ConvertResult(newValue);
@@ -152,5 +162,10 @@ public HiLoValue(long low, long high)
public HiLoValue NextValue()
=> new HiLoValue(Low + 1, High);
}
+
+ ///
+ /// Releases the allocated resources for this instance.
+ ///
+ public virtual void Dispose() => _semaphoreSlim.Dispose();
}
}