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

为何多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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:35:54