mirror of
https://github.com/stride3d/stride.git
synced 2026-10-12 02:23:23 +00:00
* [Core.Mathematics] Modernize code * [Core.Mathematics] Add tests to Stride.Tests.Simple * [Core] Cleanup some code * [Core.CompilerServices] Cleanup some code * [Core.CompilerServices] Add tests to Stride.Tests.Simple * [Core.IO] Modernize code * [Core.MicroThreading] Modernize code * [Core.Serialization] Modernize code * [Core.AssemblyProcessor.Tests] Modernize code * [Core.CompilerServices.Tests] Modernize code * [Core.Mathematics.Tests] Modernize code * [Core.Tests] Modernize code * [Core.Translation] Modernize code * [Core.Reflection] Modernize code * Fixes build of Stride.VisualStudio.sln * [Core.Yaml.Tests] Modernize code * [Core.Design] Modernize code * [Core.Design.Tests] Modernize code * [Core.Mathematics] More annotations * Fix build of projects that import code from Core.Design Especially those targeting .NET Framework
231 lines
8.0 KiB
C#
231 lines
8.0 KiB
C#
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
|
|
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
|
|
|
|
using Stride.Core.MicroThreading;
|
|
|
|
namespace Stride.Core;
|
|
|
|
/// <summary>
|
|
/// An hybrid lock that allows to do asynchrounous work when acquired from a <see cref="MicroThread"/>, and still allow to await for acquisition out of a
|
|
/// microthread. This lock support re-entrancy.
|
|
/// </summary>
|
|
public class MicroThreadLock : IDisposable
|
|
{
|
|
private readonly MicroThreadLocal<MicroThreadAsyncLock> asyncLocks = new();
|
|
private readonly Queue<MicroThreadLockBase> lockQueue = [];
|
|
private readonly object syncLock = new();
|
|
private int currentSyncLockThread;
|
|
private MicroThreadSyncLock currentSyncLock;
|
|
private bool isDisposed;
|
|
|
|
/// <inheritdoc/>
|
|
public void Dispose()
|
|
{
|
|
isDisposed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reserves the lock in order to use synchronous locking. The lock will be bound to the calling thread and therefore should be released on the same thread.
|
|
/// </summary>
|
|
/// <returns>A task that completes when the lock is reserved. The result of the task is an <see cref="ISyncLockable"/> object allowing to do the lock.</returns>
|
|
public async Task<ISyncLockable> ReserveSyncLock()
|
|
{
|
|
#if NET7_0_OR_GREATER
|
|
ObjectDisposedException.ThrowIf(isDisposed, this);
|
|
#else
|
|
if (isDisposed) throw new ObjectDisposedException(nameof(MicroThreadLock));
|
|
#endif
|
|
|
|
// If we already acquired the lock in this thread, we're just re-entering
|
|
if (currentSyncLockThread == Environment.CurrentManagedThreadId)
|
|
{
|
|
var currentLock = currentSyncLock;
|
|
// Reentering will happen in the Lock() method
|
|
return currentLock;
|
|
}
|
|
|
|
// Select the proper type of lock depending on whether we're in a micro-thread or not.
|
|
var newLock = new MicroThreadSyncLock(this);
|
|
AcquireOrEnqueue(newLock);
|
|
await newLock.Acquired;
|
|
|
|
// In the case of sync, we need to register in the proper thread, so call to Register() is defered to later.
|
|
return newLock;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Acquires an asynchronous lock. The lock will be tied to the current <see cref="MicroThread"/> to allow re-entrancy.
|
|
/// </summary>
|
|
/// <returns>A task that completes when the lock is acquired.</returns>
|
|
/// <remarks>This way of acquiring the lock is only valid when in a <see cref="MicroThread"/>.</remarks>
|
|
public async Task<IDisposable> LockAsync()
|
|
{
|
|
if (Scheduler.CurrentMicroThread == null) throw new InvalidOperationException($"Aynchronous lock can only be acquired from a micro-thread. Use {nameof(ReserveSyncLock)}.");
|
|
#if NET7_0_OR_GREATER
|
|
ObjectDisposedException.ThrowIf(isDisposed, this);
|
|
#else
|
|
if (isDisposed) throw new ObjectDisposedException(nameof(MicroThreadLock));
|
|
#endif
|
|
|
|
// If we already acquired the lock in this micro-thread, we're just re-entering
|
|
if (asyncLocks.IsValueCreated && asyncLocks.Value != null)
|
|
{
|
|
var currentLock = asyncLocks.Value;
|
|
currentLock.Reenter();
|
|
return currentLock;
|
|
}
|
|
|
|
// Select the proper type of lock depending on whether we're in a micro-thread or not.
|
|
var newLock = new MicroThreadAsyncLock(this);
|
|
AcquireOrEnqueue(newLock);
|
|
await newLock.Acquired;
|
|
|
|
// In the case of async we can register immediately after acquiring. For sync, we need to register in the proper thread.
|
|
newLock.Register();
|
|
|
|
return newLock;
|
|
}
|
|
|
|
private void AcquireOrEnqueue(MicroThreadLockBase lockToAcquire)
|
|
{
|
|
lock (lockQueue)
|
|
{
|
|
if (lockQueue.Count == 0)
|
|
{
|
|
// Nothing else is in the queue, let's acquire immediately.
|
|
lockToAcquire.Acquire();
|
|
}
|
|
// Let's enqueue this new lock so it can be notified by the previous lock when it can be acquired.
|
|
lockQueue.Enqueue(lockToAcquire);
|
|
}
|
|
}
|
|
|
|
private abstract class MicroThreadLockBase : IDisposable
|
|
{
|
|
protected readonly MicroThreadLock MicroThreadLock;
|
|
private readonly TaskCompletionSource<int> acquisition;
|
|
private int reentrancy;
|
|
|
|
protected MicroThreadLockBase(MicroThreadLock microThreadLock)
|
|
{
|
|
MicroThreadLock = microThreadLock;
|
|
acquisition = new TaskCompletionSource<int>();
|
|
}
|
|
|
|
public Task Acquired => acquisition.Task;
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
if (reentrancy == 0)
|
|
throw new InvalidOperationException("Trying to dispose a lock that has already been released.");
|
|
|
|
--reentrancy;
|
|
if (reentrancy == 0)
|
|
{
|
|
Release();
|
|
lock (MicroThreadLock.lockQueue)
|
|
{
|
|
// Remove ourself from the queue.
|
|
var thisLock = MicroThreadLock.lockQueue.Dequeue();
|
|
if (thisLock != this) throw new InvalidOperationException("The first lock in the queue was not the current lock");
|
|
// If another lock is waiting, let's acquire it
|
|
if (MicroThreadLock.lockQueue.Count > 0)
|
|
{
|
|
var nextLock = MicroThreadLock.lockQueue.Peek();
|
|
nextLock.Acquire();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void Acquire()
|
|
{
|
|
if (reentrancy != 0) throw new InvalidOperationException("Trying to enter a lock that has already been entered");
|
|
++reentrancy;
|
|
acquisition.SetResult(0);
|
|
}
|
|
|
|
internal virtual void Reenter()
|
|
{
|
|
if (!acquisition.Task.IsCompleted) throw new InvalidOperationException("Trying to reenter a lock that has not yet been acquired");
|
|
++reentrancy;
|
|
}
|
|
|
|
internal abstract void Release();
|
|
}
|
|
|
|
private class MicroThreadAsyncLock : MicroThreadLockBase
|
|
{
|
|
public MicroThreadAsyncLock(MicroThreadLock microThreadLock)
|
|
: base(microThreadLock)
|
|
{
|
|
}
|
|
|
|
internal void Register()
|
|
{
|
|
MicroThreadLock.asyncLocks.Value = this;
|
|
}
|
|
|
|
internal override void Release()
|
|
{
|
|
MicroThreadLock.asyncLocks.Value = null;
|
|
}
|
|
}
|
|
|
|
private class MicroThreadSyncLock : MicroThreadLockBase, ISyncLockable
|
|
{
|
|
private bool locked;
|
|
|
|
public MicroThreadSyncLock(MicroThreadLock microThreadLock)
|
|
: base(microThreadLock)
|
|
{
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
Monitor.Exit(MicroThreadLock.syncLock);
|
|
base.Dispose();
|
|
}
|
|
|
|
internal override void Reenter()
|
|
{
|
|
Monitor.Enter(MicroThreadLock.syncLock);
|
|
base.Reenter();
|
|
}
|
|
|
|
internal void Take()
|
|
{
|
|
if (MicroThreadLock.currentSyncLockThread != 0)
|
|
throw new InvalidOperationException("Trying to lock while another thread owns the lock.");
|
|
|
|
MicroThreadLock.currentSyncLockThread = Environment.CurrentManagedThreadId;
|
|
MicroThreadLock.currentSyncLock = this;
|
|
}
|
|
|
|
internal override void Release()
|
|
{
|
|
if (MicroThreadLock.currentSyncLockThread != Environment.CurrentManagedThreadId)
|
|
throw new InvalidOperationException("Trying to unlock while another thread owns the lock.");
|
|
|
|
MicroThreadLock.currentSyncLockThread = 0;
|
|
MicroThreadLock.currentSyncLock = null;
|
|
}
|
|
|
|
public IDisposable Lock()
|
|
{
|
|
if (!locked)
|
|
{
|
|
// We register here because we are in the proper thread.
|
|
Take();
|
|
Monitor.Enter(MicroThreadLock.syncLock);
|
|
}
|
|
else
|
|
{
|
|
Reenter();
|
|
}
|
|
locked = true;
|
|
return this;
|
|
}
|
|
}
|
|
}
|