如何在不阻塞的情况下等待触发事件的长时间运行任务?
问题分析与解决方案
你的事件处理程序未被调用,核心原因是:CLLocationManager的事件分发依赖线程的运行循环(RunLoop),而你用WaitHandle.WaitOne()直接阻塞了线程,导致RunLoop无法运转,系统无法将事件传递给处理方法。
解决方案一:保持线程RunLoop活跃(非异步方式)
修改Scanner方法,避免直接阻塞线程,通过循环等待取消令牌的同时,让RunLoop持续处理事件:
private void Scanner() { this.scanner = new CLLocationManager(); this.scanner.DidRangeBeacons += HandleDidRangeBeacons; // 补充定位权限请求、扫描区域设置等初始化逻辑 ... try { // 循环等待取消,每次等待100ms后触发一次RunLoop事件处理 while (!this.scannerCancellationTokenSource.Token.WaitHandle.WaitOne(100)) { // 让当前线程的RunLoop处理待办事件,确保回调能被触发 NSRunLoop.Current.RunUntil(NSDate.FromTimeIntervalSinceNow(0.1)); } } catch(OperationCanceledException) { } finally { // 清理资源,避免内存泄漏 this.scanner.DidRangeBeacons -= HandleDidRangeBeacons; this.scanner.StopRangingBeacons(null); this.scanner.Dispose(); } }
解决方案二:用async/await(更优雅,且不会阻塞启动线程)
你对async/await的理解有误:async/await不会阻塞启动和停止的调用线程,它的设计初衷就是避免阻塞。改造后的代码如下:
// 启动时直接调用异步方法,立即返回Task,不会阻塞调用线程 this.scannerTask = ScannerAsync(); private async Task ScannerAsync() { this.scanner = new CLLocationManager(); this.scanner.DidRangeBeacons += HandleDidRangeBeacons; // 补充初始化逻辑 ... try { // 用Task.Delay配合取消令牌等待,不会阻塞线程,RunLoop正常运转 await Task.Delay(Timeout.Infinite, this.scannerCancellationTokenSource.Token); } catch(OperationCanceledException) { } finally { // 清理资源 this.scanner.DidRangeBeacons -= HandleDidRangeBeacons; this.scanner.StopRangingBeacons(null); this.scanner.Dispose(); } } public void StopScanner() { this.scannerCancellationTokenSource.Cancel(); }
调用ScannerAsync()时,方法会立即返回Task对象,启动线程无需等待扫描逻辑执行完成,完全满足你“不阻塞启动/停止线程”的需求。
关键注意点
CLLocationManager的事件回调默认会在创建它的线程的RunLoop上触发,必须保证该线程的RunLoop处于活跃状态,不能被无限阻塞——这是你的代码无法触发事件的核心原因。
内容的提问来源于stack exchange,提问作者anon
相关产品推荐
相关产品推荐

