初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.Utils.Threading;
|
||||
|
||||
// 使用 AI 生成的代码
|
||||
// 时间: 2025/9/2
|
||||
// 模型: GPT-5
|
||||
|
||||
/// <summary>
|
||||
/// 一个带配额(Permit)的 <see cref="System.Threading.AutoResetEvent"/> 变体。
|
||||
/// 支持一次性释放多个等待任务。
|
||||
/// 类似于 <see cref="System.Threading.SemaphoreSlim"/>,但语义更接近 AutoResetEvent。
|
||||
/// </summary>
|
||||
public sealed class AsyncCountResetEvent : IDisposable
|
||||
{
|
||||
private readonly Queue<TaskCompletionSource<bool>> _waiters = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当前剩余的配额数。如果 > 0,新的等待者会立即通过。
|
||||
/// </summary>
|
||||
private int _permits;
|
||||
|
||||
/// <summary>
|
||||
/// 标记当前对象是否已释放。
|
||||
/// </summary>
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数。
|
||||
/// </summary>
|
||||
~AsyncCountResetEvent()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待一个信号。当信号可用时返回完成的 <see cref="Task"/>。
|
||||
/// 如果没有信号,则进入队列等待。
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 一个 <see cref="Task"/>,表示等待操作。
|
||||
/// 如果对象被释放,则返回的 Task 会异常结束。
|
||||
/// </returns>
|
||||
public Task WaitAsync()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, nameof(AsyncCountResetEvent));
|
||||
|
||||
if (_permits > 0)
|
||||
{
|
||||
_permits--;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(
|
||||
TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_waiters.Enqueue(tcs);
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放一个或多个信号,让等待者继续执行。
|
||||
/// </summary>
|
||||
/// <param name="count">要释放的配额数量,默认为 1。</param>
|
||||
public void Set(int count = 1)
|
||||
{
|
||||
if (count <= 0) return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
while (count > 0 && _waiters.Count > 0)
|
||||
{
|
||||
var tcs = _waiters.Dequeue();
|
||||
tcs.TrySetResult(true);
|
||||
count--;
|
||||
}
|
||||
|
||||
// 如果没有等待者,就累积到配额里
|
||||
_permits += count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象。
|
||||
/// 会让所有等待中的任务异常完成。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
while (_waiters.Count > 0)
|
||||
{
|
||||
var tcs = _waiters.Dequeue();
|
||||
tcs.TrySetException(new ObjectDisposedException(nameof(AsyncCountResetEvent)));
|
||||
}
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.Utils.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// 可重置的异步延时器。在指定延时后执行异步任务并等待下一次重置后重复该逻辑,指定延时未到达时重置将会重新开始计时。
|
||||
/// <p>实例创建后并不会立即开始计时,而是等待第一次 <see cref="ResetAsync"/>
|
||||
/// 调用。因此,若有特殊需求,请不要忘了创建实例后调用一次 <see cref="ResetAsync"/>。</p>
|
||||
/// </summary>
|
||||
public class AsyncDebounce(CancellationToken cancelToken = default) : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行延迟。
|
||||
/// </summary>
|
||||
public required TimeSpan Delay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步任务实例。
|
||||
/// </summary>
|
||||
public required Func<Task> ScheduledTask { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 指示本次延迟任务是否已经完成。
|
||||
/// </summary>
|
||||
public bool IsCurrentTaskCompleted { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 指示本次延迟任务是否正在运行。
|
||||
/// </summary>
|
||||
public bool IsCurrentTaskRunning => _currentTask is not null;
|
||||
|
||||
private Task? _currentTask;
|
||||
private Task? _worker; // 跟踪最近一次 worker
|
||||
private CancellationTokenSource? _currentDelayCts;
|
||||
private readonly CancellationTokenSource _cts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken);
|
||||
private readonly object _resetLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// 重置延时。
|
||||
/// </summary>
|
||||
public async Task ResetAsync()
|
||||
{
|
||||
IsCurrentTaskCompleted = false;
|
||||
|
||||
CancellationTokenSource? capturedCts;
|
||||
Task? runningToAwait;
|
||||
|
||||
#pragma warning disable VSTHRD103 // 禁用检查 避免智障警告
|
||||
lock (_resetLock)
|
||||
{
|
||||
// 只取消,不在这里 Dispose
|
||||
try { _currentDelayCts?.Cancel(); }
|
||||
catch(ObjectDisposedException) { /* ignored */ }
|
||||
|
||||
_currentDelayCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
|
||||
capturedCts = _currentDelayCts;
|
||||
|
||||
// 记录当前运行中的 ScheduledTask,稍后在锁外等待,避免重叠
|
||||
runningToAwait = _currentTask;
|
||||
|
||||
_worker = Task.Run(async () =>
|
||||
{
|
||||
try { await Task.Delay(Delay, capturedCts.Token).ConfigureAwait(false); }
|
||||
catch (OperationCanceledException) { return; }
|
||||
finally
|
||||
{
|
||||
// 仅由使用者自己释放,避免跨线程 Dispose 竞态
|
||||
// 注意:不要在这里释放 _cancelToken
|
||||
capturedCts.Dispose();
|
||||
}
|
||||
|
||||
// 身份校验,确保自己仍是“当前”那一次
|
||||
if (
|
||||
!ReferenceEquals(_currentDelayCts, capturedCts) ||
|
||||
capturedCts.IsCancellationRequested ||
|
||||
_cts.IsCancellationRequested
|
||||
) return;
|
||||
|
||||
if (_currentTask is not null) await _currentTask.ConfigureAwait(false);
|
||||
|
||||
var task = ScheduledTask();
|
||||
lock (_resetLock) _currentTask = task;
|
||||
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_resetLock)
|
||||
{
|
||||
_currentTask = null;
|
||||
IsCurrentTaskCompleted = true;
|
||||
}
|
||||
}
|
||||
}, _cts.Token);
|
||||
}
|
||||
#pragma warning restore VSTHRD103
|
||||
|
||||
// 避免 ScheduledTask 并发
|
||||
if (runningToAwait is not null) await runningToAwait.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
try { _worker?.Wait(); } catch { /* ignored */ }
|
||||
_currentDelayCts?.Dispose();
|
||||
_cts.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.Utils.Threading;
|
||||
|
||||
// Partly generated by gpt-5-mini (20250808)
|
||||
public sealed class AsyncManualResetEvent : IDisposable
|
||||
{
|
||||
private readonly object _syncLock = new();
|
||||
private TaskCompletionSource<bool> _tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly ManualResetEventSlim _mre = new(false);
|
||||
private bool _disposed;
|
||||
|
||||
public AsyncManualResetEvent(bool initialState = false)
|
||||
{
|
||||
if (!initialState) return;
|
||||
_tcs.SetResult(true);
|
||||
_mre.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件是否已触发。
|
||||
/// </summary>
|
||||
public bool IsSet
|
||||
{
|
||||
get { lock (_syncLock) { return _tcs.Task.IsCompleted; } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">用于结束等待的取消信号</param>
|
||||
public Task WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
TaskCompletionSource<bool> t;
|
||||
lock (_syncLock) { t = _tcs; }
|
||||
if (!cancellationToken.CanBeCanceled || t.Task.IsCompleted) return t.Task;
|
||||
return _WaitWithCancellationAsync(t.Task, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task _WaitWithCancellationAsync(Task waitTask, CancellationToken ct)
|
||||
{
|
||||
var cancelTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using (ct.Register(s => ((TaskCompletionSource<bool>)s!).TrySetResult(true), cancelTcs))
|
||||
{
|
||||
var completed = await Task.WhenAny(waitTask, cancelTcs.Task).ConfigureAwait(false);
|
||||
if (completed == cancelTcs.Task) ct.ThrowIfCancellationRequested();
|
||||
await waitTask.ConfigureAwait(false); // propagate exceptions if any
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步等待。
|
||||
/// </summary>
|
||||
public void Wait() => _mre.Wait();
|
||||
|
||||
/// <summary>
|
||||
/// 同步等待,并在超时后结束。
|
||||
/// </summary>
|
||||
/// <param name="millisecondsTimeout">等待超时的毫秒数</param>
|
||||
/// <returns>若已触发事件则为 <c>true</c>,否则为 <c>false</c></returns>
|
||||
public bool Wait(int millisecondsTimeout) => _mre.Wait(millisecondsTimeout);
|
||||
|
||||
/// <summary>
|
||||
/// 同步等待,并在超时后结束。
|
||||
/// </summary>
|
||||
/// <param name="timeout">等待超时</param>
|
||||
/// <returns>若已触发事件则为 <c>true</c>,否则为 <c>false</c></returns>
|
||||
public bool Wait(TimeSpan timeout) => _mre.Wait(timeout);
|
||||
|
||||
/// <summary>
|
||||
/// 同步等待,并传递用于结束等待的取消信号。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">用于结束等待的取消信号</param>
|
||||
public void Wait(CancellationToken cancellationToken) => _mre.Wait(cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件。
|
||||
/// </summary>
|
||||
public void Set()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
_tcs.TrySetResult(true); // Use TrySetResult to avoid exceptions on repeated Set
|
||||
_mre.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置事件。
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (!_tcs.Task.IsCompleted) return; // already reset
|
||||
_tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_mre.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_mre.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.Utils.Threading;
|
||||
|
||||
// Partly generated by o4-mini-high (20250709)
|
||||
|
||||
/// <summary>
|
||||
/// 使用两个线程池的调度器,分为 CPU 线程池和 IO 线程池,分别负责 CPU 密集型任务和 IO 密集型任务
|
||||
/// </summary>
|
||||
public class DualThreadPool
|
||||
{
|
||||
/// <summary>
|
||||
/// 两线程池分别计算的最大线程数
|
||||
/// </summary>
|
||||
public int MaxThread { get; }
|
||||
|
||||
private readonly TaskFactory _ioFactory;
|
||||
private readonly TaskFactory _cpuFactory;
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="DualThreadPool"/> 实例
|
||||
/// </summary>
|
||||
/// <param name="maxThread">参考 <see cref="MaxThread"/>,最小为 1</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">最大线程数小于 1</exception>
|
||||
public DualThreadPool(int maxThread)
|
||||
{
|
||||
if (maxThread < 1) throw new ArgumentOutOfRangeException(nameof(maxThread));
|
||||
|
||||
MaxThread = maxThread;
|
||||
|
||||
var ioScheduler = new LimitedConcurrencyLevelTaskScheduler(maxThread);
|
||||
var cpuScheduler = new LimitedConcurrencyLevelTaskScheduler(maxThread);
|
||||
var cancellationToken = _cts.Token;
|
||||
|
||||
// DenyChildAttach 防止子任务跑到外层 scheduler
|
||||
_ioFactory = new TaskFactory(
|
||||
cancellationToken,
|
||||
TaskCreationOptions.DenyChildAttach,
|
||||
TaskContinuationOptions.None,
|
||||
ioScheduler);
|
||||
|
||||
_cpuFactory = new TaskFactory(
|
||||
cancellationToken,
|
||||
TaskCreationOptions.DenyChildAttach,
|
||||
TaskContinuationOptions.None,
|
||||
cpuScheduler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交一段 IO 密集工作
|
||||
/// </summary>
|
||||
public Task QueueIo(Action work) => _ioFactory.StartNew(work);
|
||||
|
||||
/// <summary>
|
||||
/// 提交一段异步 IO 密集工作
|
||||
/// </summary>
|
||||
public Task QueueIo(Func<Task> work) => _ioFactory.StartNew(work).Unwrap();
|
||||
|
||||
/// <summary>
|
||||
/// 提交一段 CPU 密集工作
|
||||
/// </summary>
|
||||
public Task QueueCpu(Action work) => _cpuFactory.StartNew(work);
|
||||
|
||||
/// <summary>
|
||||
/// 提交一段异步 CPU 密集工作
|
||||
/// </summary>
|
||||
public Task QueueCpu(Func<Task> work) => _cpuFactory.StartNew(work).Unwrap();
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有正在执行的工作
|
||||
/// </summary>
|
||||
public void CancelAll() => _cts.Cancel();
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.Utils.Threading;
|
||||
|
||||
// Partly generated by o4-mini-high (20250709)
|
||||
|
||||
/// <summary>
|
||||
/// 允许限制最大并发度的 TaskScheduler
|
||||
/// </summary>
|
||||
public sealed class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static bool _currentThreadIsProcessingItems;
|
||||
|
||||
private readonly LinkedList<Task> _tasks = [];
|
||||
private int _delegatesQueuedOrRunning = 0;
|
||||
private readonly int _maxDegreeOfParallelism;
|
||||
|
||||
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
|
||||
{
|
||||
if (maxDegreeOfParallelism < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
|
||||
_maxDegreeOfParallelism = maxDegreeOfParallelism;
|
||||
}
|
||||
|
||||
public override int MaximumConcurrencyLevel => _maxDegreeOfParallelism;
|
||||
|
||||
protected override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.TryEnter(_tasks, ref lockTaken);
|
||||
if (lockTaken) return _tasks.ToArray();
|
||||
else throw new NotSupportedException();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) Monitor.Exit(_tasks);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void QueueTask(Task task)
|
||||
{
|
||||
lock (_tasks)
|
||||
{
|
||||
_tasks.AddLast(task);
|
||||
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
|
||||
{
|
||||
_delegatesQueuedOrRunning++;
|
||||
ThreadPool.UnsafeQueueUserWorkItem(_ => _ProcessTasks(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _ProcessTasks()
|
||||
{
|
||||
_currentThreadIsProcessingItems = true;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Task item;
|
||||
lock (_tasks)
|
||||
{
|
||||
if (_tasks.Count == 0)
|
||||
{
|
||||
_delegatesQueuedOrRunning--;
|
||||
break;
|
||||
}
|
||||
item = _tasks.First!.Value;
|
||||
_tasks.RemoveFirst();
|
||||
}
|
||||
TryExecuteTask(item);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentThreadIsProcessingItems = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
if (!_currentThreadIsProcessingItems) return false;
|
||||
|
||||
if (taskWasPreviouslyQueued)
|
||||
{
|
||||
return TryDequeue(task) && TryExecuteTask(task);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TryExecuteTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryDequeue(Task task)
|
||||
{
|
||||
lock (_tasks)
|
||||
{
|
||||
return _tasks.Remove(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.Utils.Threading;
|
||||
|
||||
// Partly generated by o4-mini-high (20250709)
|
||||
|
||||
/// <summary>
|
||||
/// 可限制并发线程数量的任务池。
|
||||
/// </summary>
|
||||
public class LimitedTaskPool
|
||||
{
|
||||
/// <summary>
|
||||
/// 最大并发线程数。
|
||||
/// </summary>
|
||||
public int MaxThread { get; }
|
||||
|
||||
private readonly TaskFactory _factory;
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="LimitedTaskPool"/> 实例
|
||||
/// </summary>
|
||||
/// <param name="maxThread">参考 <see cref="MaxThread"/>,最小为 1</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="maxThread"/> 小于 1</exception>
|
||||
public LimitedTaskPool(int maxThread)
|
||||
{
|
||||
if (maxThread < 1) throw new ArgumentOutOfRangeException(nameof(maxThread));
|
||||
|
||||
MaxThread = maxThread;
|
||||
|
||||
var scheduler = new LimitedConcurrencyLevelTaskScheduler(maxThread);
|
||||
var cancellationToken = _cts.Token;
|
||||
|
||||
_factory = new TaskFactory(
|
||||
cancellationToken,
|
||||
TaskCreationOptions.DenyChildAttach,
|
||||
TaskContinuationOptions.None,
|
||||
scheduler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交一个任务。
|
||||
/// </summary>
|
||||
public Task Submit(Action work) => _factory.StartNew(work);
|
||||
|
||||
/// <summary>
|
||||
/// 提交一个异步任务。
|
||||
/// </summary>
|
||||
public Task Submit(Func<Task> work) => _factory.StartNew(work).Unwrap();
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有任务。
|
||||
/// </summary>
|
||||
public void CancelAll() => _cts.Cancel();
|
||||
}
|
||||
Reference in New Issue
Block a user