fix(logging): 修复异步日志输出器刷新机制并增强线程安全性

- 实现了基于信号量的可靠Flush完成通知机制
- 添加了OnFlushCompleted事件用于监控刷新操作结果
- 修复了BindaleProperty的线程安全问题,添加锁保护
- 将协程异常回调改为异步执行,防止阻塞调度器主循环
- 优化了AsyncLogAppender的资源清理逻辑
- 增强了Flush方法的超时处理机制
This commit is contained in:
GeWuYou 2026-03-04 10:56:11 +08:00 committed by gewuyou
parent f984f4a600
commit d88aa12014
3 changed files with 62 additions and 25 deletions

View File

@ -35,6 +35,7 @@ public sealed class CoroutineScheduler(
/// <summary>
/// 协程异常处理回调,当协程执行过程中发生异常时触发
/// 注意:事件处理程序会在独立任务中异步调用,以避免阻塞调度器主循环
/// </summary>
public event Action<CoroutineHandle, Exception>? OnCoroutineException;
@ -386,16 +387,23 @@ public sealed class CoroutineScheduler(
var slot = _slots[slotIndex];
var handle = slot?.Handle ?? default;
// 将异常回调派发到线程池,避免阻塞调度器主循环
var handler = OnCoroutineException;
if (handler != null)
{
Task.Run(() =>
{
try
{
// 触发异常回调
OnCoroutineException?.Invoke(handle, ex);
handler(handle, ex);
}
catch (Exception callbackEx)
{
// 防止回调异常导致调度器崩溃
// 防止回调异常传播,记录到控制台
Console.Error.WriteLine($"[CoroutineScheduler] Exception in error callback: {callbackEx}");
}
});
}
// 输出到控制台作为后备
Console.Error.WriteLine($"[CoroutineScheduler] Coroutine {handle} failed with exception: {ex}");

View File

@ -10,9 +10,11 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
{
private readonly Channel<LogEntry> _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;
/// <summary>
/// 创建异步日志输出器
@ -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
/// <summary>
/// 刷新缓冲区ILogAppender 接口实现)
/// 注意此方法会阻塞直到所有待处理日志写入完成或超时默认30秒
/// 超时结果可通过 OnFlushCompleted 事件观察
/// </summary>
void ILogAppender.Flush()
{
Flush();
var success = Flush();
OnFlushCompleted?.Invoke(success);
}
/// <summary>
/// Flush 操作完成事件参数指示是否成功true或超时false
/// </summary>
public event Action<bool>? OnFlushCompleted;
/// <summary>
/// 刷新缓冲区,等待所有日志写入完成
/// 使用信号量机制确保可靠的完成通知,避免竞态条件
/// </summary>
/// <param name="timeout">超时时间默认30秒</param>
/// <returns>是否成功刷新所有日志</returns>
/// <returns>是否成功刷新所有日志true=成功false=超时)</returns>
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)
// 等待处理任务发出完成信号
var success = _flushSemaphore.Wait(actualTimeout);
OnFlushCompleted?.Invoke(success);
return success;
}
finally
{
return false; // 超时
_flushRequested = false;
}
spinWait.SpinOnce();
}
_innerAppender.Flush();
return true;
}
/// <summary>
@ -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)

View File

@ -66,9 +66,12 @@ public class BindableProperty<T>(T defaultValue = default!) : IBindableProperty<
/// </summary>
/// <param name="newValue">新的属性值</param>
public void SetValueWithoutEvent(T newValue)
{
lock (_lock)
{
MValue = newValue;
}
}
/// <summary>
/// 实现IEasyEvent接口的注册方法将无参事件转换为有参事件处理
@ -154,9 +157,12 @@ public class BindableProperty<T>(T defaultValue = default!) : IBindableProperty<
/// </summary>
/// <returns>当前属性值</returns>
protected virtual T GetValue()
{
lock (_lock)
{
return MValue;
}
}
/// <summary>
/// 返回属性值的字符串表示形式