使用Task.WhenAny()结合Task.Delay()未触发超时问题排查
Task.WhenAny搭配Task.Delay在Windows 10 22H2平板上无法触发超时的问题解决
我遇到一个BLE相关的AsyncOperation挂起问题,为添加超时处理实现了标准Task.WhenAny模式,但在Windows 10 22H2平板上,当BLE任务挂起后,Task.Delay(20000)无法按时完成,导致程序永久挂起、无法进入超时分支;开发PC上运行完全正常。即使改用System.Runtime.WindowsRuntime内置的AsTask()方法,问题依旧存在。
原实现代码
GattDeviceServicesResult services = null; var task = bleDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached).AsTask(); if (await Task.WhenAny(task, Task.Delay(20000)) == task) { Console.WriteLine($"Found Services for device:"); services = task.Result; if (services.Services != null) { foreach (var service in services.Services) { Console.WriteLine($"Service: {service.Uuid}"); } } } else { Console.WriteLine("Timed Out while getting service list."); }
自定义AsTask实现(问题复现时也试过系统内置版本)
public static Task<TResult> AsTask<TResult>(this IAsyncOperation<TResult> operation) { var tcs = new TaskCompletionSource<TResult>(); operation.Completed += delegate { switch(operation.Status) { case AsyncStatus.Completed: tcs.TrySetResult(operation.GetResults()); break; case AsyncStatus.Error: tcs.TrySetException(operation.ErrorCode); break; case AsyncStatus.Canceled: tcs.SetCanceled(); break; } }; return tcs.Task; }
问题分析
该问题大概率和Windows 10 22H2版本中BLE相关的线程调度异常有关:当原BLE操作挂起时,可能占用了UI线程或特定同步上下文,导致Task.Delay的回调无法被正常触发,进而超时逻辑失效。
解决方案
方案1:用CancellationToken主动取消原操作
给超时逻辑绑定CancellationToken,超时后主动取消BLE操作,避免资源占用,同时确保超时分支能执行:
GattDeviceServicesResult services = null; using var cts = new CancellationTokenSource(20000); var bleOperation = bleDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached); var task = bleOperation.AsTask(cts.Token); try { services = await task; Console.WriteLine($"Found Services for device:"); if (services.Services != null) { foreach (var service in services.Services) { Console.WriteLine($"Service: {service.Uuid}"); } } } catch (OperationCanceledException) { Console.WriteLine("Timed Out while getting service list."); bleOperation.Cancel(); // 主动取消原AsyncOperation释放资源 } catch (Exception ex) { Console.WriteLine($"Error getting services: {ex.Message}"); }
方案2:脱离同步上下文执行超时逻辑
使用ConfigureAwait(false)避免绑定当前同步上下文,确保Task.Delay能在后台线程正常触发:
GattDeviceServicesResult services = null; var task = bleDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached).AsTask(); var completedTask = await Task.WhenAny(task, Task.Delay(20000)).ConfigureAwait(false); if (completedTask == task) { services = await task.ConfigureAwait(false); Console.WriteLine($"Found Services for device:"); if (services.Services != null) { foreach (var service in services.Services) { Console.WriteLine($"Service: {service.Uuid}"); } } } else { Console.WriteLine("Timed Out while getting service list."); }
方案3:使用WinRT原生超时机制
部分WinRT异步操作支持直接设置Timeout属性,利用原生机制处理超时:
GattDeviceServicesResult services = null; var bleOperation = bleDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached); bleOperation.Timeout = 20000; // 针对支持Timeout属性的WinRT操作 try { services = await bleOperation.AsTask(); Console.WriteLine($"Found Services for device:"); if (services.Services != null) { foreach (var service in services.Services) { Console.WriteLine($"Service: {service.Uuid}"); } } } catch (Exception ex) { if (ex.HResult == unchecked((int)0x800705B4)) // WinRT超时错误码 { Console.WriteLine("Timed Out while getting service list."); } else { Console.WriteLine($"Error getting services: {ex.Message}"); } }
内容的提问来源于stack exchange,提问作者komodosp
相关产品推荐
相关产品推荐

