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

TaskCompletionSource封装异步方法时Dispose导致await后代码不执行

问题原因

你遇到的挂起问题本质是TaskCompletionSource(以下简称TCS)关联的任务永远无法进入完成状态,结合你描述的两个复现条件,具体原因可以拆解为以下几点:

  1. 回调逻辑覆盖不全:你当前的回调仅处理了ScanFail和ScanComplete两个通知码,其他场景(比如资源释放触发的中断通知、未知通知)到达时不会给TCS设置状态,导致任务永远处于 pending 状态,await会无限等待。
  2. 回调无异常捕获逻辑:如果回调触发时_wlanInterface已经被释放,访问实例的操作会抛出异常,而事件回调中的异常通常会被第三方WiFi库吞掉,不会触发TCS的SetException,同样会导致任务永远无法完成。
  3. 资源释放时序冲突:你删除Dispose后逻辑可正常运行,说明Dispose的执行实际早于扫描完成回调触发:多数WLAN封装库在Dispose时会主动注销所有事件、清空待派发的通知队列,导致你注册的扫描完成回调永远不会被执行,TCS无法完成。
  4. 同步上下文适配问题:原生WLAN API的通知依赖Windows消息队列派发,控制台程序默认没有消息循环。同步调用GetResult()时,主线程处于阻塞状态,底层库会临时调度消息派发让回调正常触发;而await交出线程控制权后,没有消息循环处理通知,导致回调无法触发。
解决方案

1. 完善TCS的边界处理逻辑

修改ScanNetworksAsync方法,补全异常捕获、取消监听、释放监听逻辑,同时给TCS指定RunContinuationsAsynchronously避免回调阻塞通知线程:

public Task ScanNetworksAsync(CancellationToken cancellationToken = default)
{
    // 配置TCS避免续期阻塞通知线程
    var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    
    void Callback(Wlan.WlanNotificationData data)
    {
        try
        {
            switch (data.notificationCode)
            {
                case (int) Wlan.WlanNotificationCodeAcm.ScanFail:
                    _wlanInterface!.WlanNotification -= Callback;
                    tcs.TrySetResult(false);
                    break;
                case (int) Wlan.WlanNotificationCodeAcm.ScanComplete:
                    _wlanInterface!.WlanNotification -= Callback;
                    tcs.TrySetResult(true);
                    break;
            }
        }
        catch (Exception ex)
        {
            _wlanInterface!.WlanNotification -= Callback;
            tcs.TrySetException(ex);
        }
    }

    // 监听外部取消请求
    var cancellationReg = cancellationToken.Register(() =>
    {
        _wlanInterface!.WlanNotification -= Callback;
        tcs.TrySetCanceled(cancellationToken);
    });

    // 监听对象释放事件(需要你在封装类中添加Disposed事件,Dispose时触发)
    Disposed += (_, _) =>
    {
        _wlanInterface!.WlanNotification -= Callback;
        tcs.TrySetException(new ObjectDisposedException(nameof(WiFiClient)));
    };

    _wlanInterface!.WlanNotification += Callback;

    try
    {
        _wlanInterface.Scan();
        // 任务完成后释放取消注册资源
        tcs.Task.ContinueWith(_ => cancellationReg.Dispose(), cancellationToken);
    }
    catch (Exception e)
    {
        _wlanInterface.WlanNotification -= Callback;
        cancellationReg.Dispose();
        tcs.TrySetException(e);
    }

    return tcs.Task;
}

2. 调整Dispose逻辑避免提前销毁资源

如果扫描完成前不需要释放资源,可以在Dispose时等待扫描任务完成,避免通知被清空:

private Task? _ongoingScanTask;

// 扫描方法中添加正在运行的任务标记
public Task ScanNetworksAsync(CancellationToken cancellationToken = default)
{
    // ... 其他逻辑不变
    _ongoingScanTask = tcs.Task;
    _ongoingScanTask.ContinueWith(_ => _ongoingScanTask = null, cancellationToken);
    return _ongoingScanTask;
}

public void Dispose()
{
    // 最多等待1秒扫描结束,避免无限阻塞
    _ongoingScanTask?.Wait(1000);
    ((IDisposable) _internalClient).Dispose();
}

3. 适配控制台程序的消息循环

可以在Main方法入口强制指定线程池同步上下文,让通知可以在线程池派发:

private static async Task Main(string[] args)
{
    // 入口第一行添加
    SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
    
    _container = new Startup().BuildContainer();
    // ... 其他逻辑不变
}

4. 调整依赖注入Scope的释放时机

如果后续SubscribeEvents方法也需要用到WiFi客户端,可以把客户端注册为全局单例,或者把后续逻辑移到using块内部,避免Scope释放导致资源被提前销毁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 00:45:01