为何多Background Worker的Ping检测操作未并行执行?
WPF多机器Ping检测未并行执行的原因及解决方法
问题场景
使用C# WPF开发机器监控应用,为每个Physical_Machine实例分配独立的BackgroundWorker执行Ping检测,但实际运行时Ping操作是串行执行的:离线机器会逐个等待2秒超时,20台机器需等待40秒才能全部更新状态,不符合预期的并行执行逻辑。
原代码示例
Physical_Machine类
public class Physical_Machine : INotifyPropertyChanged { public Brush Color_Background { get { return color_Background; } set { color_Background = value; OnPropertyChanged(); } } private Brush color_Background; private readonly BackgroundWorker wkVerif = new BackgroundWorker(); private string Name; private string IP; public Physical_Machine(string _name, string _ip) { Name = _name; IP=_ip; wkVerif.DoWork += Action_WkVerif; wkVerif.ProgressChanged += Progress_WkVerif; wkVerif.WorkerReportsProgress = true; wkVerif.WorkerSupportsCancellation = true; wkVerif.RunWorkerAsync(); } private void Action_WkVerif(object sender, DoWorkEventArgs e) { while (true) { try { using (Ping pinger = new Ping()) { PingReply reply = pinger.Send(IP, 2000); wkVerif.ReportProgress(0, reply); } } catch (PingException piex) { wkVerif.ReportProgress(0, piex); } } } private void Progress_WkVerif(object sender, ProgressChangedEventArgs e) { if (e.UserState is PingReply lareply) { switch (lareply.Status) { case IPStatus.Success: Color_Background = new SolidColorBrush(Color.FromArgb(255, 177, 254, 183)); break; case IPStatus.TimedOut: case IPStatus.DestinationHostUnreachable: Color_Background = new SolidColorBrush(Color.FromArgb(255, 180, 180, 180)); break; default: Color_Background = new SolidColorBrush(Color.FromArgb(255, 255, 194, 130)); break; } } if (e.UserState is PingException lexception) { Color_Background = new SolidColorBrush(Color.FromArgb(255, 255, 93, 81)); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
主窗口集合定义
public ObservableCollection<Physical_Machine> Physical_Machines { get { return physical_machines; } set { physical_machines= value; OnPropertyChanged(); } } private ObservableCollection<Physical_Machine> physical_machines= new ObservableCollection<Physical_Machine>();
实例化代码
Physical_Machines.Add(new Physical_Machine("SRV01", "192.168.1.101")); Physical_Machines.Add(new Physical_Machine("SRV02", "192.168.1.102")); Physical_Machines.Add(new Physical_Machine("SRV03", "192.168.1.103")); ... Physical_Machines.Add(new Physical_Machine("SRV21", "192.168.1.121"));
未并行的核心原因
- 线程池调度限制+同步阻塞调用:
BackgroundWorker依赖.NET线程池,线程池默认最小工作线程数等于CPU核心数。每个BackgroundWorker的DoWork方法中调用同步的Ping.Send,会阻塞线程池线程2秒;线程池扩容有延迟(默认每500ms新增一个线程),导致20个Ping任务被串行排队执行。 - 构造函数同步启动的时序问题:在UI线程同步创建20个
Physical_Machine实例,每个实例在构造函数中立即启动BackgroundWorker,短时间内大量任务提交到线程池,进一步加剧了任务排队。
解决方案
推荐方案:改用异步非阻塞Ping(替代BackgroundWorker)
使用Ping.SendPingAsync异步方法,避免阻塞线程池线程,真正实现并行执行:
using System.ComponentModel; using System.Net.NetworkInformation; using System.Windows.Media; using System.Threading; using System.Runtime.CompilerServices; public class Physical_Machine : INotifyPropertyChanged { public Brush Color_Background { get => color_Background; set { color_Background = value; OnPropertyChanged(); } } private Brush color_Background; private readonly string _name; private readonly string _ip; private CancellationTokenSource _cts; public Physical_Machine(string name, string ip) { _name = name; _ip = ip; _cts = new CancellationTokenSource(); // 启动异步Ping循环,无需等待 _ = RunPingLoopAsync(_cts.Token); } private async Task RunPingLoopAsync(CancellationToken token) { // 复用Ping实例,减少资源开销 using var pinger = new Ping(); while (!token.IsCancellationRequested) { try { PingReply reply = await pinger.SendPingAsync(_ip, 2000); UpdateMachineStatus(reply); } catch (PingException) { // 处理Ping异常(如网络不可用) Color_Background = new SolidColorBrush(Color.FromArgb(255, 255, 93, 81)); } // 每次Ping后间隔1秒,避免过度占用网络 await Task.Delay(1000, token); } } private void UpdateMachineStatus(PingReply reply) { switch (reply.Status) { case IPStatus.Success: Color_Background = new SolidColorBrush(Color.FromArgb(255, 177, 254, 183)); break; case IPStatus.TimedOut: case IPStatus.DestinationHostUnreachable: Color_Background = new SolidColorBrush(Color.FromArgb(255, 180, 180, 180)); break; default: Color_Background = new SolidColorBrush(Color.FromArgb(255, 255, 194, 130)); break; } } // 提供停止Ping的方法,用于清理资源 public void StopMonitoring() { _cts?.Cancel(); _cts?.Dispose(); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
方案优势
SendPingAsync是真正的异步网络操作,不会阻塞线程池线程,20个Ping任务可同时执行。- 复用
Ping实例,减少对象创建销毁的开销。 - 通过
CancellationToken灵活控制Ping循环的启停,资源清理更规范。
备选方案:调整线程池设置(不推荐)
若坚持使用BackgroundWorker,可在应用启动时增加线程池最小工作线程数,减少排队:
// 在App.xaml.cs的OnStartup方法中添加 protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 设置线程池最小工作线程和IO线程数为25,足够容纳20个Ping任务 ThreadPool.SetMinThreads(25, 25); }
注意事项
线程池是全局资源,调整其设置可能影响应用中其他依赖线程池的代码,且同步阻塞的方式依然会占用线程资源,效率远低于异步方案。
内容的提问来源于stack exchange,提问作者Ionnae
相关产品推荐
相关产品推荐

