From d88aa12014e44c9cb382de7b97b14d3e5e144e2e Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Wed, 4 Mar 2026 10:56:11 +0800
Subject: [PATCH] =?UTF-8?q?fix(logging):=20=E4=BF=AE=E5=A4=8D=E5=BC=82?=
=?UTF-8?q?=E6=AD=A5=E6=97=A5=E5=BF=97=E8=BE=93=E5=87=BA=E5=99=A8=E5=88=B7?=
=?UTF-8?q?=E6=96=B0=E6=9C=BA=E5=88=B6=E5=B9=B6=E5=A2=9E=E5=BC=BA=E7=BA=BF?=
=?UTF-8?q?=E7=A8=8B=E5=AE=89=E5=85=A8=E6=80=A7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 实现了基于信号量的可靠Flush完成通知机制
- 添加了OnFlushCompleted事件用于监控刷新操作结果
- 修复了BindaleProperty的线程安全问题,添加锁保护
- 将协程异常回调改为异步执行,防止阻塞调度器主循环
- 优化了AsyncLogAppender的资源清理逻辑
- 增强了Flush方法的超时处理机制
---
.../coroutine/CoroutineScheduler.cs | 24 ++++++---
.../logging/appenders/AsyncLogAppender.cs | 53 +++++++++++++------
GFramework.Core/property/BindableProperty.cs | 10 +++-
3 files changed, 62 insertions(+), 25 deletions(-)
diff --git a/GFramework.Core/coroutine/CoroutineScheduler.cs b/GFramework.Core/coroutine/CoroutineScheduler.cs
index 18e5c2a..eb7e04a 100644
--- a/GFramework.Core/coroutine/CoroutineScheduler.cs
+++ b/GFramework.Core/coroutine/CoroutineScheduler.cs
@@ -35,6 +35,7 @@ public sealed class CoroutineScheduler(
///
/// 协程异常处理回调,当协程执行过程中发生异常时触发
+ /// 注意:事件处理程序会在独立任务中异步调用,以避免阻塞调度器主循环
///
public event Action? OnCoroutineException;
@@ -386,15 +387,22 @@ public sealed class CoroutineScheduler(
var slot = _slots[slotIndex];
var handle = slot?.Handle ?? default;
- try
+ // 将异常回调派发到线程池,避免阻塞调度器主循环
+ var handler = OnCoroutineException;
+ if (handler != null)
{
- // 触发异常回调
- OnCoroutineException?.Invoke(handle, ex);
- }
- catch (Exception callbackEx)
- {
- // 防止回调异常导致调度器崩溃
- Console.Error.WriteLine($"[CoroutineScheduler] Exception in error callback: {callbackEx}");
+ Task.Run(() =>
+ {
+ try
+ {
+ handler(handle, ex);
+ }
+ catch (Exception callbackEx)
+ {
+ // 防止回调异常传播,记录到控制台
+ Console.Error.WriteLine($"[CoroutineScheduler] Exception in error callback: {callbackEx}");
+ }
+ });
}
// 输出到控制台作为后备
diff --git a/GFramework.Core/logging/appenders/AsyncLogAppender.cs b/GFramework.Core/logging/appenders/AsyncLogAppender.cs
index fa35d73..fd78a75 100644
--- a/GFramework.Core/logging/appenders/AsyncLogAppender.cs
+++ b/GFramework.Core/logging/appenders/AsyncLogAppender.cs
@@ -10,9 +10,11 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
{
private readonly Channel _channel;
private readonly CancellationTokenSource _cts;
+ private readonly SemaphoreSlim _flushSemaphore = new(0, 1);
private readonly ILogAppender _innerAppender;
private readonly Task _processingTask;
private bool _disposed;
+ private volatile bool _flushRequested;
///
/// 创建异步日志输出器
@@ -74,6 +76,7 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
}
_cts.Dispose();
+ _flushSemaphore.Dispose();
_disposed = true;
}
@@ -92,38 +95,46 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
///
/// 刷新缓冲区(ILogAppender 接口实现)
+ /// 注意:此方法会阻塞直到所有待处理日志写入完成,或超时(默认30秒)
+ /// 超时结果可通过 OnFlushCompleted 事件观察
///
void ILogAppender.Flush()
{
- Flush();
+ var success = Flush();
+ OnFlushCompleted?.Invoke(success);
}
+ ///
+ /// Flush 操作完成事件,参数指示是否成功(true)或超时(false)
+ ///
+ public event Action? OnFlushCompleted;
+
///
/// 刷新缓冲区,等待所有日志写入完成
+ /// 使用信号量机制确保可靠的完成通知,避免竞态条件
///
/// 超时时间(默认30秒)
- /// 是否成功刷新所有日志
+ /// 是否成功刷新所有日志(true=成功,false=超时)
public bool Flush(TimeSpan? timeout = null)
{
if (_disposed) return false;
var actualTimeout = timeout ?? TimeSpan.FromSeconds(30);
- var startTime = DateTime.UtcNow;
- // 使用 SpinWait 替代忙轮询,更高效
- var spinWait = new SpinWait();
- while (_channel.Reader.Count > 0)
+ // 请求刷新
+ _flushRequested = true;
+
+ try
{
- if (DateTime.UtcNow - startTime > actualTimeout)
- {
- return false; // 超时
- }
-
- spinWait.SpinOnce();
+ // 等待处理任务发出完成信号
+ var success = _flushSemaphore.Wait(actualTimeout);
+ OnFlushCompleted?.Invoke(success);
+ return success;
+ }
+ finally
+ {
+ _flushRequested = false;
}
-
- _innerAppender.Flush();
- return true;
}
///
@@ -144,6 +155,18 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
// 记录内部错误到控制台(避免递归)
await Console.Error.WriteLineAsync($"[AsyncLogAppender] Error processing log entry: {ex.Message}");
}
+
+ // 检查是否有刷新请求且通道已空
+ if (_flushRequested && _channel.Reader.Count == 0)
+ {
+ _innerAppender.Flush();
+
+ // 发出完成信号
+ if (_flushSemaphore.CurrentCount == 0)
+ {
+ _flushSemaphore.Release();
+ }
+ }
}
}
catch (OperationCanceledException)
diff --git a/GFramework.Core/property/BindableProperty.cs b/GFramework.Core/property/BindableProperty.cs
index 7034e72..d3e69bd 100644
--- a/GFramework.Core/property/BindableProperty.cs
+++ b/GFramework.Core/property/BindableProperty.cs
@@ -67,7 +67,10 @@ public class BindableProperty(T defaultValue = default!) : IBindableProperty<
/// 新的属性值
public void SetValueWithoutEvent(T newValue)
{
- MValue = newValue;
+ lock (_lock)
+ {
+ MValue = newValue;
+ }
}
///
@@ -155,7 +158,10 @@ public class BindableProperty(T defaultValue = default!) : IBindableProperty<
/// 当前属性值
protected virtual T GetValue()
{
- return MValue;
+ lock (_lock)
+ {
+ return MValue;
+ }
}
///