mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
fix(logging): 修复异步日志输出器刷新机制并增强线程安全性
- 实现了基于信号量的可靠Flush完成通知机制 - 添加了OnFlushCompleted事件用于监控刷新操作结果 - 修复了BindaleProperty的线程安全问题,添加锁保护 - 将协程异常回调改为异步执行,防止阻塞调度器主循环 - 优化了AsyncLogAppender的资源清理逻辑 - 增强了Flush方法的超时处理机制
This commit is contained in:
parent
f984f4a600
commit
d88aa12014
@ -35,6 +35,7 @@ public sealed class CoroutineScheduler(
|
||||
|
||||
/// <summary>
|
||||
/// 协程异常处理回调,当协程执行过程中发生异常时触发
|
||||
/// 注意:事件处理程序会在独立任务中异步调用,以避免阻塞调度器主循环
|
||||
/// </summary>
|
||||
public event Action<CoroutineHandle, Exception>? 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}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 输出到控制台作为后备
|
||||
|
||||
@ -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)
|
||||
{
|
||||
return false; // 超时
|
||||
}
|
||||
|
||||
spinWait.SpinOnce();
|
||||
// 等待处理任务发出完成信号
|
||||
var success = _flushSemaphore.Wait(actualTimeout);
|
||||
OnFlushCompleted?.Invoke(success);
|
||||
return success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_flushRequested = false;
|
||||
}
|
||||
|
||||
_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)
|
||||
|
||||
@ -67,7 +67,10 @@ public class BindableProperty<T>(T defaultValue = default!) : IBindableProperty<
|
||||
/// <param name="newValue">新的属性值</param>
|
||||
public void SetValueWithoutEvent(T newValue)
|
||||
{
|
||||
MValue = newValue;
|
||||
lock (_lock)
|
||||
{
|
||||
MValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -155,7 +158,10 @@ public class BindableProperty<T>(T defaultValue = default!) : IBindableProperty<
|
||||
/// <returns>当前属性值</returns>
|
||||
protected virtual T GetValue()
|
||||
{
|
||||
return MValue;
|
||||
lock (_lock)
|
||||
{
|
||||
return MValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user