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

WPF端口扫描器异步任务执行时UI仍冻结问题排查

WPF端口扫描器UI卡顿问题排查

开发WPF端口扫描器时,始终无法实现流畅的UI体验:程序启动时流畅,但运行一段时间后UI会冻结,直到所有扫描线程结束才恢复。


相关代码片段

展示扫描结果的DataGrid

<DataGrid Grid.Column="0" ItemsSource="{Binding NetScanResults}" Margin="5" AutoGenerateColumns="False" IsReadOnly="True">

扫描结果实体类

public class NetScanResult : ObservableObject
{
    private IPAddress ipAddress;
    public IPAddress IpAddress
    {
        get => ipAddress;
        set
        {
            SetProperty(ref ipAddress, value);
            OnPropertyChanged(nameof(IpAddressStr));
        }
    }

    private ObservableCollection<int> openPorts;
    public ObservableCollection<int> OpenPorts
    {
        get => openPorts;
        set { SetProperty(ref openPorts, value); }
    }

    public NetScanResult()
    {
        OpenPorts = new ObservableCollection<int>();
    }
}

初始扫描逻辑

[RelayCommand]
public async void NetScan2Start()
{
    try
    {
        int[] ports = { 22, 80, 443, 554, 3389, 8000, 37777 };
        int timeoutMs = 2500;

        var stopwatch = new Stopwatch();
        stopwatch.Start();

        var netScanResults = await NetUtility.ScanNetwork2_Prepare(NetScanIpRangeBegin, NetScanIpRangeEnd, NetScanNetmaskSelected, ports, timeoutMs);
        NetScanResults = new ObservableCollection<NetScanResult>(netScanResults);
        var tasks = new List<Task>();

        foreach (var netScanResult in NetScanResults)
        {
            foreach (var port in ports)
            {
                NetScanResult tempResult = netScanResult;
                int tempPort = port;

                var task = ScanPortAsync(tempResult, tempPort, timeoutMs);

                tasks.Add(task);
            }
        }

        await Task.WhenAll(tasks.ToArray());

        stopwatch.Stop();
        MessageBox.Show($"Scan finished in {stopwatch.ElapsedMilliseconds}ms");
    }
    catch (Exception ex)
    {
        ShowInvalidData(ex.Message);
        return;
    }
}


private async Task ScanPortAsync(NetScanResult result, int port, int timeoutMs)
{
    try
    {
        Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        var connectResult = socket.BeginConnect(result.IpAddress, port, null, null);

        await Task.Run(() => { connectResult.AsyncWaitHandle.WaitOne(timeoutMs, true); });

        if (socket.Connected)
        {
            socket.EndConnect(connectResult);

            Application.Current.Dispatcher.Invoke(() =>
            {
                result.OpenPorts.Add(port);
            });
            
            Debug.WriteLine($"{result.IpAddress}:{port} - open.");
        }

        socket.Close();
    }
    catch (Exception ex)
    {
        Debug.WriteLine($" Exception happened =( - {ex.Message}");
    } 
}

更新后的改进与新问题

已按建议修改代码:添加进度显示计时器、简化端口扫描逻辑,引入SemaphoreSlim限制并发任务数为5,改用Dispatcher.BeginInvoke更新UI。但UI冻结问题仅缓解未彻底解决,同时扫描耗时大幅增加。

调试输出频繁出现以下异常:
Exception thrown: 'System.Net.Sockets.SocketException' in System.Net.Sockets.dll
Exception thrown: 'System.Net.Sockets.SocketException' in System.Private.CoreLib.dll

尽管ConnectAsync已被try-catch包裹,怀疑这些异常会拖慢程序。若移除信号量或把并发数提高到7及以上,UI几乎无响应,仅偶尔能显示更新。设备CPU为Intel i5-1135g7,仍在寻找UI冻结根源。

更新后的扫描逻辑代码

private int progressStep;
private int progress;
private int progressTotal;
private DispatcherTimer progressTimer;

[RelayCommand]
public async void NetScan3Start() => await Task.Run(async () =>
{

    int[] ports = { 22, 80, 443, 554, 3389, 8000, 37777 };
    int timeoutMs = 250;

    var stopwatch = new Stopwatch();
    stopwatch.Start();

    IList<IPAddress> addresses = await NetUtility.ScanNetwork2_Prepare(NetScanIpRangeBegin, NetScanIpRangeEnd, NetScanNetmaskSelected);

    //
    var list = new List<NetScanResult2>();

    foreach (var address in addresses)
    {
        list.Add(new NetScanResult2(address));
    }

    progressTotal = list.Count * ports.Length;
    progressStep = (int)(list.Count * ports.Length * 0.05); //5% of total amount;
    progress = 0;

    _ = Application.Current.Dispatcher.BeginInvoke(new Action(() =>
    {
        NetScanResults = new ObservableCollection<NetScanResult2>(list);
    }));

    progressTimer = new DispatcherTimer(DispatcherPriority.Normal, Application.Current.Dispatcher);
    progressTimer.Interval = TimeSpan.FromSeconds(1);
    progressTimer.Tick += ProgressTimer_Tick;
    progressTimer.Start();
    Application.Current.Dispatcher.Thread.Priority = ThreadPriority.Highest;


    //
    var scanTasks = new List<Task>();

    var semaphore = new SemaphoreSlim(5);

    foreach (var netScanResult in list)
    {
        var tmpNetScanResult = netScanResult;
        var tmpAddress = netScanResult.IpAddress;

        foreach (var port in ports)
        {
            var tmpPort = port;

            var scanTask = Task.Run(async () =>
            {
                await semaphore.WaitAsync();

                try
                {
                    var client = new TcpClient();

                    var connectTask = client.ConnectAsync(tmpAddress, tmpPort);
                    var delayTask = Task.Delay(timeoutMs);

                    var result = await Task.WhenAny(connectTask, delayTask);

                    if (result == connectTask && client.Connected)
                    {
                        _ = Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                        {
                            tmpNetScanResult.OpenPorts.Add(tmpPort);
                        }));
                    }

                    client.Dispose();
                }
                catch (SocketException)
                {
                }
                catch (Exception ex)
                {
                }
                finally
                {
                    ++progress;
                    semaphore.Release();
                }
            });

            scanTasks.Add(scanTask);
        }
    }

    await Task.WhenAll(scanTasks);

    stopwatch.Stop();
    progressTimer.Stop();
    MessageBox.Show($"Finished in {stopwatch.ElapsedMilliseconds}ms");
});

private void ProgressTimer_Tick(object? sender, EventArgs e)
{
    NetScanAmount = progressTotal - progress;
}

内容的提问来源于stack exchange,提问作者CorellianAle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:47:04