如何实现带返回值能力的AutoResetEvent?求成熟方案与代码优化建议
ChromiumWebBrowser操作同步方案与自定义类优化建议
需求场景
需在代码一处调用WaitOne(),另外两处调用Set(),基于ChromiumWebBrowser实现操作同步:
- Place1:调用浏览器操作的位置
- Place2:浏览器状态变为
IsLoading = false的位置 - Place3:浏览器错误事件处理器,需将操作执行中的错误信息传递到Place1,在
Wait()后抛出异常
现有自定义类实现
已实现自定义CustomAutoResetEvent类(代码如下),通过私有变量_message在类内传递信息:
public class CustomAutoResetEvent { int? _waitTimeout = null; public CustomAutoResetEvent(int? waitTimeout = null) { _waitTimeout = waitTimeout; } // 考虑从多个点触发Set,一旦某点触发后,其他点不再改变状态 bool _isSetted = false; AutoResetEvent _autoResetEvent = new AutoResetEvent(false); string _message { get; set; } = null; public void WaitOneThrow() // Place 1 { _isSetted = false; if (_waitTimeout == null) _autoResetEvent.WaitOne(); else _autoResetEvent.WaitOne(_waitTimeout.Value); if (!string.IsNullOrEmpty(_message)) { string msg = null; msg = _message; _message = null; //if (msg == "ERR_ABORTED") // return; throw new Exception(msg); } } public void Set(string errMessage = null) // Place 2, 3 { if (!_isSetted) { _message = errMessage; _autoResetEvent.Set(); _autoResetEvent.Reset(); _isSetted = true; } } }
现寻求:
- 满足该需求的成熟解决方案
- 现有自定义类的优化建议
一、成熟解决方案实现
针对ChromiumWebBrowser的操作同步需求,用TaskCompletionSource<T>替代自定义事件类是更简洁可靠的方案,它原生支持异步等待、结果/异常传递,无需手动管理信号量状态:
实现步骤
- Place1的调用与等待逻辑
// 声明TaskCompletionSource,用于绑定操作完成/错误状态 var tcs = new TaskCompletionSource<bool>(); // 绑定浏览器事件 browser.IsLoadingChanged += OnLoadingChanged; browser.LoadError += OnLoadError; try { // 执行目标浏览器操作(如导航) browser.LoadUrl("https://example.com"); // 等待操作完成或错误触发 await tcs.Task; } catch (Exception ex) { // 处理Place3传递的错误信息 Console.WriteLine($"浏览器操作失败:{ex.Message}"); } finally { // 解绑事件,避免内存泄漏 browser.IsLoadingChanged -= OnLoadingChanged; browser.LoadError -= OnLoadError; }
- Place2的IsLoading状态变更处理
private void OnLoadingChanged(object sender, EventArgs e) { var browser = (ChromiumWebBrowser)sender; if (!browser.IsLoading) { // 加载完成,标记任务成功 tcs.TrySetResult(true); } }
- Place3的错误事件处理
private void OnLoadError(object sender, LoadErrorEventArgs e) { // 将错误信息包装为异常,传递给Place1 tcs.TrySetException(new Exception($"加载错误:{e.ErrorCode} - {e.ErrorText}")); }
方案优势
- 原生适配.NET异步编程模型,代码可读性高
TrySetResult/TrySetException天然保证只触发一次,避免重复设置- 内置异常传递机制,无需自定义消息变量
- 无需手动管理信号量的重置/触发逻辑,减少出错概率
二、现有CustomAutoResetEvent类的优化建议
如果坚持使用自定义事件类,可从线程安全、逻辑合理性、API设计三个维度优化:
1. 线程安全优化
当前类中_isSetted、_message变量未加锁,多线程环境下会出现竞态条件,需添加线程同步锁:
2. 逻辑合理性修复
- 原代码
Set方法中调用Set()后立即Reset(),会导致WaitOne()无法接收到信号,这是严重逻辑错误,必须移除手动Reset()——AutoResetEvent的特性就是在WaitOne()获取信号后自动重置状态 - 添加超时判断:原代码未处理超时情况,需在
WaitOne()返回false时抛出超时异常
3. API设计优化
- 重命名方法:
WaitOneThrow改为WaitAndThrowIfError,更直观表达方法意图 - 封装超时参数:将
_waitTimeout改为只读字段,避免后续修改 - 支持取消令牌:增加接受
CancellationToken的重载,增强异步场景下的可控性
优化后的完整代码
public class CustomAutoResetEvent { private readonly int? _waitTimeout; private readonly AutoResetEvent _autoResetEvent = new AutoResetEvent(false); private readonly object _lockObj = new object(); private bool _isSetted; private string _errorMessage; public CustomAutoResetEvent(int? waitTimeout = null) { _waitTimeout = waitTimeout; } public void WaitAndThrowIfError(CancellationToken cancellationToken = default) { lock (_lockObj) { _isSetted = false; _errorMessage = null; } // 结合取消令牌处理等待逻辑 var waitHandles = new WaitHandle[] { _autoResetEvent, cancellationToken.WaitHandle }; var waitResult = WaitHandle.WaitAny(waitHandles, _waitTimeout ?? Timeout.Infinite); if (waitResult == 1) { cancellationToken.ThrowIfCancellationRequested(); } else if (waitResult == WaitHandle.WaitTimeout) { throw new TimeoutException("等待操作超时"); } string errorMsg; lock (_lockObj) { errorMsg = _errorMessage; _errorMessage = null; } if (!string.IsNullOrEmpty(errorMsg)) { throw new Exception(errorMsg); } } public void Set(string errorMessage = null) { lock (_lockObj) { if (_isSetted) return; _errorMessage = errorMessage; _isSetted = true; } _autoResetEvent.Set(); } }
内容的提问来源于stack exchange,提问作者Maksim Rudnev
相关产品推荐
相关产品推荐

