Xamarin中Task.WhenAll执行批量Ping时偶发无异常崩溃问题求助
问题:Xamarin应用网段Ping扫描偶发崩溃(SIGBUS)
我正在开发一款基于Xamarin的应用,用于通过SMB2查找已连接PC的IP地址。通过await Dns.GetHostAddressesAsync(Dns.GetHostName())获取本机IP的前三段,随后Ping该网段内0-255的所有IP(如xxx.xxx.xxx.0到xxx.xxx.xxx.255)检测响应。
为每个IP创建独立Task执行Ping(共255个),调用Task.WhenAll(tasks)时偶发崩溃,每3-5次运行触发一次,且无.NET层面的异常信息抛出。
相关代码
PingAsyncCustom方法
async Task PingAsyncCustom (List<String> testIPs) { if (Battery.PowerSource == BatteryPowerSource.Usb) { List<Task> tasks = new List<Task>(); foreach (string ips in testIPs) { using (Ping ping = new Ping()) { var taskPing = InitiatePing(ips, ping); tasks.Add(taskPing); } } try { await Task.WhenAll(tasks); } catch (Exception e) { Crashes.TrackError(e); Analytics.TrackEvent("WhenAll Crash" + e.ToString()); } Report("Waiting Finished"); tasks.RemoveAll(x => x.IsCompleted); Report("Ping Async Finished"); } else { Report("Disconected during Ping Sweep"); pIps.Clear(); } }
InitiatePing方法
private async Task InitiatePing (String ip, Ping ping) { var tcs = new TaskCompletionSource<PingReply>(); var options = new PingOptions(64,true); string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; byte[] buffer = Encoding.ASCII.GetBytes(data); try { Report("InitiatePing: " + ip); var reply = await ping.SendPingAsync(ip, 2000, buffer, options); if (reply.Status == IPStatus.Success) { lock (lockObj) { pIps.Add(reply); } } } catch(PingException e) { Report("PingException: " + e.ToString()); } }
运行时错误信息
Thread started: <Thread Pool> #99 Thread started: <Thread Pool> #100 Thread started: <Thread Pool> #101 Thread started: <Thread Pool> #102 Thread started: <Thread Pool> #103 Thread started: <Thread Pool> #104 Thread started: <Thread Pool> #105 Thread started: <Thread Pool> #106 Thread started: <Thread Pool> #107 [libc] Fatal signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr
问题原因及解决方案
核心原因
- Ping对象提前释放:
PingAsyncCustom的foreach循环中,using会在每次循环结束时立即释放Ping对象,但异步的InitiatePing仍在使用该对象,导致访问已释放的非托管资源,触发底层内存访问错误(SIGBUS)。 - 线程池过载:一次性启动255个Ping任务,瞬间耗尽线程池资源,引发系统层面的内存异常。
- 底层异常未被捕获:Xamarin的
Ping类依赖非托管资源,这类底层错误无法被.NET的try-catch捕获。
解决方案
1. 修正Ping对象生命周期
将Ping对象的创建和释放移到InitiatePing内部,确保异步操作完成后再释放资源:
private async Task InitiatePing(string ip) { var options = new PingOptions(64, true); string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; byte[] buffer = Encoding.ASCII.GetBytes(data); try { Report("InitiatePing: " + ip); using (var ping = new Ping()) { var reply = await ping.SendPingAsync(ip, 2000, buffer, options); if (reply.Status == IPStatus.Success) { lock (lockObj) { pIps.Add(reply); } } } } catch (PingException e) { Report("PingException: " + e.ToString()); } catch (Exception e) { Report("Unexpected ping error: " + e.ToString()); Crashes.TrackError(e); } }
2. 限制并发任务数量
用SemaphoreSlim控制并发数(建议10-20个),避免线程池过载:
async Task PingAsyncCustom(List<string> testIPs) { if (Battery.PowerSource == BatteryPowerSource.Usb) { var semaphore = new SemaphoreSlim(15); // 限制15个并发任务 var tasks = new List<Task>(); foreach (var ip in testIPs) { tasks.Add(Task.Run(async () => { await semaphore.WaitAsync(); try { await InitiatePing(ip); } finally { semaphore.Release(); } })); } try { await Task.WhenAll(tasks); } catch (Exception e) { Crashes.TrackError(e); Analytics.TrackEvent("WhenAll Crash: " + e.ToString()); } Report("Ping Async Finished"); } else { Report("Disconnected during Ping Sweep"); pIps.Clear(); } }
3. 更优替代方案:ARP扫描
相比Ping扫描,ARP扫描更高效(无需等待ICMP响应),且不会触发大量线程创建。在Xamarin中可通过DependencyService调用Android/iOS原生ARP接口,直接获取局域网内活跃设备的IP和MAC地址,从根源避免Ping扫描的资源问题。
内容的提问来源于stack exchange,提问作者Ekcior
相关产品推荐
相关产品推荐

