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

C#如何异步向WhenAny添加任务实现Playwright请求监听超时

实现逻辑

核心用TaskCompletionSource<T>实现,这类原语就是专门为「任务触发时机不确定、需要在任意回调中手动控制任务完成状态」的异步场景设计的,完美适配动态事件监听+超时等待的需求,不需要提前预注册所有待等待的任务。

之前尝试的方案不适用的原因很明确:

  • Task.WhenAny要求传入所有已知的Task实例,但请求是运行时动态生成的,触发前根本拿不到对应的Task实例
  • Monitor.TryEnter、信号量都属于线程同步原语,适配的是多线程阻塞等待场景,和异步事件回调的编程模型不匹配,硬写很容易出现死锁、异常丢失的问题
完整实现代码
string? result = null;
// 初始化30秒超时的取消源
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
// 初始化TCS,指定异步执行延续任务,避免阻塞事件触发线程
var tcs = new TaskCompletionSource<string?>(TaskCreationOptions.RunContinuationsAsynchronously);

// 新页面事件处理器
void OnNewPage(object? _, IPage newPage)
{
    // 页面请求事件处理器,因为Playwright事件是无返回值的EventHandler签名,这里用async void是合规写法
    async void OnPageRequest(object? _, IRequest request)
    {
        try
        {
            // 不匹配目标特征直接跳过
            if (!request.Url.Contains("some_identifier")) return;
            // TCS已经完成(已经拿到结果/超时/报错)就不再处理
            if (tcs.Task.IsCompleted) return;

            await newPage.GotoAsync(request.Url);
            var targetContent = await newPage.InnerTextAsync("body");
            // 标记任务完成,传入结果
            tcs.TrySetResult(targetContent);
        }
        catch (Exception ex)
        {
            // 捕获执行过程中的异常,传递到等待线程
            tcs.TrySetException(ex);
        }
    }

    newPage.Request += OnPageRequest;
    // 任务结束后自动解绑请求事件,避免内存泄漏
    _ = tcs.Task.ContinueWith(_ => newPage.Request -= OnPageRequest, TaskScheduler.Default);
}

context.Page += OnNewPage;
// 任务结束后自动解绑新页面事件
_ = tcs.Task.ContinueWith(_ => context.Page -= OnNewPage, TaskScheduler.Default);
// 超时触发时自动标记TCS完成,返回null
timeoutCts.Token.Register(() => tcs.TrySetResult(null));

try
{
    await mainPage.Locator("a#somelink").ClickAsync();
    // 等待拿到结果或者超时
    result = await tcs.Task;
}
finally
{
    // 释放超时资源
    timeoutCts.Dispose();
}

return result;
注意事项
  • 所有设置TCS状态的操作都用TrySet*系列方法,避免多个请求同时匹配特征时重复设置状态抛出异常
  • async void的事件处理器必须包裹try/catch,否则异常会直接抛到线程池未被捕获,导致程序崩溃
  • 如果需要单独区分超时场景,可以把超时回调里的逻辑改成tcs.TrySetCanceled(),在外层捕获OperationCanceledException处理超时逻辑即可
  • 必须在任务结束后解绑注册的事件监听器,否则会造成内存泄漏,后续其他操作触发对应事件时也会误执行这段逻辑

内容的提问来源于stack exchange,提问作者Uxonith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 20:48:14