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>
|
||||||
/// 协程异常处理回调,当协程执行过程中发生异常时触发
|
/// 协程异常处理回调,当协程执行过程中发生异常时触发
|
||||||
|
/// 注意:事件处理程序会在独立任务中异步调用,以避免阻塞调度器主循环
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<CoroutineHandle, Exception>? OnCoroutineException;
|
public event Action<CoroutineHandle, Exception>? OnCoroutineException;
|
||||||
|
|
||||||
@ -386,15 +387,22 @@ public sealed class CoroutineScheduler(
|
|||||||
var slot = _slots[slotIndex];
|
var slot = _slots[slotIndex];
|
||||||
var handle = slot?.Handle ?? default;
|
var handle = slot?.Handle ?? default;
|
||||||
|
|
||||||
try
|
// 将异常回调派发到线程池,避免阻塞调度器主循环
|
||||||
|
var handler = OnCoroutineException;
|
||||||
|
if (handler != null)
|
||||||
{
|
{
|
||||||
// 触发异常回调
|
Task.Run(() =>
|
||||||
OnCoroutineException?.Invoke(handle, ex);
|
{
|
||||||
}
|
try
|
||||||
catch (Exception callbackEx)
|
{
|
||||||
{
|
handler(handle, ex);
|
||||||
// 防止回调异常导致调度器崩溃
|
}
|
||||||
Console.Error.WriteLine($"[CoroutineScheduler] Exception in error callback: {callbackEx}");
|
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 Channel<LogEntry> _channel;
|
||||||
private readonly CancellationTokenSource _cts;
|
private readonly CancellationTokenSource _cts;
|
||||||
|
private readonly SemaphoreSlim _flushSemaphore = new(0, 1);
|
||||||
private readonly ILogAppender _innerAppender;
|
private readonly ILogAppender _innerAppender;
|
||||||
private readonly Task _processingTask;
|
private readonly Task _processingTask;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
private volatile bool _flushRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建异步日志输出器
|
/// 创建异步日志输出器
|
||||||
@ -74,6 +76,7 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
|
_flushSemaphore.Dispose();
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,38 +95,46 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 刷新缓冲区(ILogAppender 接口实现)
|
/// 刷新缓冲区(ILogAppender 接口实现)
|
||||||
|
/// 注意:此方法会阻塞直到所有待处理日志写入完成,或超时(默认30秒)
|
||||||
|
/// 超时结果可通过 OnFlushCompleted 事件观察
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ILogAppender.Flush()
|
void ILogAppender.Flush()
|
||||||
{
|
{
|
||||||
Flush();
|
var success = Flush();
|
||||||
|
OnFlushCompleted?.Invoke(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flush 操作完成事件,参数指示是否成功(true)或超时(false)
|
||||||
|
/// </summary>
|
||||||
|
public event Action<bool>? OnFlushCompleted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 刷新缓冲区,等待所有日志写入完成
|
/// 刷新缓冲区,等待所有日志写入完成
|
||||||
|
/// 使用信号量机制确保可靠的完成通知,避免竞态条件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="timeout">超时时间(默认30秒)</param>
|
/// <param name="timeout">超时时间(默认30秒)</param>
|
||||||
/// <returns>是否成功刷新所有日志</returns>
|
/// <returns>是否成功刷新所有日志(true=成功,false=超时)</returns>
|
||||||
public bool Flush(TimeSpan? timeout = null)
|
public bool Flush(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (_disposed) return false;
|
if (_disposed) return false;
|
||||||
|
|
||||||
var actualTimeout = timeout ?? TimeSpan.FromSeconds(30);
|
var actualTimeout = timeout ?? TimeSpan.FromSeconds(30);
|
||||||
var startTime = DateTime.UtcNow;
|
|
||||||
|
|
||||||
// 使用 SpinWait 替代忙轮询,更高效
|
// 请求刷新
|
||||||
var spinWait = new SpinWait();
|
_flushRequested = true;
|
||||||
while (_channel.Reader.Count > 0)
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (DateTime.UtcNow - startTime > actualTimeout)
|
// 等待处理任务发出完成信号
|
||||||
{
|
var success = _flushSemaphore.Wait(actualTimeout);
|
||||||
return false; // 超时
|
OnFlushCompleted?.Invoke(success);
|
||||||
}
|
return success;
|
||||||
|
}
|
||||||
spinWait.SpinOnce();
|
finally
|
||||||
|
{
|
||||||
|
_flushRequested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_innerAppender.Flush();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -144,6 +155,18 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
// 记录内部错误到控制台(避免递归)
|
// 记录内部错误到控制台(避免递归)
|
||||||
await Console.Error.WriteLineAsync($"[AsyncLogAppender] Error processing log entry: {ex.Message}");
|
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)
|
catch (OperationCanceledException)
|
||||||
|
|||||||
@ -67,7 +67,10 @@ public class BindableProperty<T>(T defaultValue = default!) : IBindableProperty<
|
|||||||
/// <param name="newValue">新的属性值</param>
|
/// <param name="newValue">新的属性值</param>
|
||||||
public void SetValueWithoutEvent(T newValue)
|
public void SetValueWithoutEvent(T newValue)
|
||||||
{
|
{
|
||||||
MValue = newValue;
|
lock (_lock)
|
||||||
|
{
|
||||||
|
MValue = newValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -155,7 +158,10 @@ public class BindableProperty<T>(T defaultValue = default!) : IBindableProperty<
|
|||||||
/// <returns>当前属性值</returns>
|
/// <returns>当前属性值</returns>
|
||||||
protected virtual T GetValue()
|
protected virtual T GetValue()
|
||||||
{
|
{
|
||||||
return MValue;
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return MValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user