You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现带返回值能力的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;
        }
    }
}

现寻求:

  1. 满足该需求的成熟解决方案
  2. 现有自定义类的优化建议

一、成熟解决方案实现

针对ChromiumWebBrowser的操作同步需求,用TaskCompletionSource<T>替代自定义事件类是更简洁可靠的方案,它原生支持异步等待、结果/异常传递,无需手动管理信号量状态:

实现步骤

  1. 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;
}
  1. Place2的IsLoading状态变更处理
private void OnLoadingChanged(object sender, EventArgs e)
{
    var browser = (ChromiumWebBrowser)sender;
    if (!browser.IsLoading)
    {
        // 加载完成,标记任务成功
        tcs.TrySetResult(true);
    }
}
  1. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 20:25:37