using System;
using System.Threading;
using System.Threading.Tasks;
namespace MW.WorkFlow
{
///
/// 一个异步事件同步原语,类似于 ManualResetEvent,但能够传递数据。
/// 可以等待事件发生并获取数据,也可以将事件设置为已发生状态并附带数据。
/// 默认情况下,事件在 Set 后保持已设置状态,直到 Reset 被调用。
///
/// 事件发生时要传递的数据类型。
public class AsyncEvent
{
// 使用 volatile 确保 _tcs 的读写操作对所有线程可见
private volatile TaskCompletionSource _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly object _lock = new object(); // 用于保护 _tcs 的替换操作
///
/// 获取一个值,指示事件是否已设置。
///
public bool IsSet => _tcs.Task.IsCompleted;
///
/// 等待事件被设置为已发生状态,并获取传递的数据。
/// 如果事件已经设置,则立即返回数据。
///
/// 用于取消等待的令牌。
/// 事件发生时传递的数据。
/// 如果在等待过程中取消令牌被请求。
public async Task WaitAsync(CancellationToken cancellationToken = default)
{
Task currentTask;
lock (_lock)
{
currentTask = _tcs.Task;
}
// 如果已经设置,直接返回结果
if (currentTask.IsCompleted)
{
return await currentTask;
}
// 注册取消回调,确保在取消时 tcs 被取消
CancellationTokenRegistration? registration = null;
if (cancellationToken.CanBeCanceled)
{
// 注意:这里需要确保在 TCS 完成或取消后,回调能被注销,避免资源泄露。
// 更好的做法是使用 CancellationToken.Register(Action) 返回的 IDisposable
// 但考虑到 TaskCompletionSource 的生命周期,通常当 Task 完成或取消时,
// 注册的回调也会在适当时候被清理。
registration = cancellationToken.Register(() =>
{
// 使用 TrySetCanceled 避免在 Task 已经完成时抛出异常
_tcs.TrySetCanceled(cancellationToken);
});
}
try
{
return await currentTask;
}
finally
{
// 确保在等待结束后注销取消回调
registration?.Dispose();
}
}
///
/// 将事件设置为已发生状态,并传递指定的数据。
/// 如果事件已经设置,此调用将被忽略。
///
/// 要传递的数据。
/// 如果事件成功设置为已发生状态,则为 true;否则为 false。
public bool Set(T data)
{
// TrySetResult 是线程安全的,如果已经设置则返回 false
// RunContinuationsAsynchronously 选项确保回调在线程池上运行,不会阻塞Set的调用者
bool result = _tcs.TrySetResult(data);
if (result)
{
Console.WriteLine($"[AsyncEvent] Event set with data: {data}");
}
return result;
}
///
/// 将事件重置为未设置状态。
/// 允许事件再次被等待。
///
public void Reset()
{
lock (_lock)
{
// 如果当前 TaskCompletionSource 尚未完成,则不应该替换它
// 因为这可能导致正在等待的 Task 永远不会完成
if (_tcs.Task.IsCompleted)
{
_tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
Console.WriteLine("[AsyncEvent] Event reset.");
}
else
{
Console.WriteLine("[AsyncEvent] Event is not yet completed, cannot reset.");
}
}
}
}
}