首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 多線程網路程式設計實戰:掃描區網線上裝置效能比較

     編輯:關於C語言

前言

網路程式常面臨阻塞等待問題:DNS 解析、連線逾時、資料傳輸等操作可能耗時數秒。單線程循序處理會導致介面凍結、回應緩慢。本文透過實際案例——掃描區網 IP 取得裝置名稱,對比單線程與多線程兩種實作,量化多線程帶來的效能提升。

核心概念:為什麼網路程式需要多線程?

  • 阻塞性操作:`Dns.GetHostEntry`、`TcpClient.Connect` 等會阻塞當前執行緒
  • 等待時間長:單一 IP 掃描可能耗時 1-3 秒,254 個 IP 需 4-12 分鐘
  • CPU 閒置:等待網路回應時 CPU 幾乎不工作,適合切換執行其他工作

Thread 類別關鍵成員速查

方法/屬性用途
Start()啟動執行緒
Join()等待執行緒結束
Sleep(ms)暫停指定毫秒
Abort()強制終止(不建議常用)
IsBackground設為背景執行緒,主程式結束時自動終止
Priority排程優先權
Name除錯用名稱

案例一:單線程循序掃描(基準版)

private void ScanSequential(string ipPrefix, int start, int end)
{
    DateTime startTime = DateTime.Now;
    listBoxResults.Items.Clear();
    progressBar.Minimum = start;
    progressBar.Maximum = end;

    for (int i = start; i <= end; i++)
    {
        string ip = $"{ipPrefix}.{i}";
        try
        {
            IPHostEntry host = Dns.GetHostEntry(ip);
            listBoxResults.Items.Add($"{ip} - {host.HostName} [線上]");
        }
        catch
        {
            listBoxResults.Items.Add($"{ip} - 無回應 [離線]");
        }
        progressBar.Value = i;
        Application.DoEvents(); // 避免 UI 凍結
    }

    TimeSpan elapsed = DateTime.Now - startTime;
    lblTime.Text = $"耗時: {elapsed.TotalSeconds:F1} 秒";
}
// 掃描 10.138.198.1 ~ 10 實測約 43 秒

案例二:多線程平行掃描(優化版)

設計重點

  1. 工作封裝:建立 `PingTask` 類別封裝單一 IP 掃描邏輯
  2. 跨執行緒 UI 更新:使用 `delegate` + `Control.Invoke` 安全更新介面
  3. 執行緒管理:建立執行緒陣列,每個 IP 一條執行緒
  4. 同步機制:使用 `lock` 保護共用資源(ListBox、ProgressBar)

完整實作

public delegate void UpdateListCallback(string ip, string hostName);

public class PingTask
{
    public string IpAddress { get; set; }
    public UpdateListCallback Callback { get; set; }

    public void Execute()
    {
        string hostName = "";
        try
        {
            IPHostEntry host = Dns.GetHostEntry(IpAddress);
            hostName = host.HostName;
        }
        catch
        {
            hostName = "主機無回應";
        }
        Callback?.Invoke(IpAddress, hostName);
    }
}

private DateTime _scanStartTime;

private void ScanParallel(string ipPrefix, int start, int end)
{
    _scanStartTime = DateTime.Now;
    listBoxResults.Items.Clear();
    
    int threadCount = end - start + 1;
    Thread[] threads = new Thread[threadCount];
    progressBar.Minimum = start;
    progressBar.Maximum = end + 1;
    progressBar.Value = start;

    for (int i = start; i <= end; i++)
    {
        int index = i - start;
        string ip = $"{ipPrefix}.{i}";
        
        var task = new PingTask
        {
            IpAddress = ip,
            Callback = UpdateResultList
        };
        
        threads[index] = new Thread(task.Execute);
        threads[index].Name = $"ScanThread_{ip}";
        threads[index].IsBackground = true; // 重要:防止主程式關閉時殘留
        threads[index].Start();
    }
}

private void UpdateResultList(string ip, string hostName)
{
    if (listBoxResults.InvokeRequired)
    {
        listBoxResults.Invoke(new UpdateListCallback(UpdateResultList), ip, hostName);
        return;
    }

    lock (listBoxResults)
    {
        listBoxResults.Items.Add($"{ip} - {hostName}");
        
        if (progressBar.Value < progressBar.Maximum)
            progressBar.Value++;
        
        if (progressBar.Value == progressBar.Maximum)
        {
            TimeSpan elapsed = DateTime.Now - _scanStartTime;
            lblTime.Text = $"耗時: {elapsed.TotalSeconds:F1} 秒";
            MessageBox.Show("掃描完成", "提示");
            progressBar.Value = progressBar.Minimum;
        }
    }
}

效能比較結果

掃描範圍單線程耗時多線程耗時提升倍數
10 IP (10.138.198.1-10)43 秒10 秒4.3x
50 IP約 3.5 分鐘約 12 秒17x
254 IP (全 C 類網段)約 18 分鐘約 15-20 秒50x+

進階優化方向

  • 執行緒池:使用 `ThreadPool.QueueUserWorkItem` 或 `Task.Run` 避免建立過多執行緒
  • 非同步模式:`Dns.GetHostEntryAsync` 搭配 `await` 實現真正的非阻塞
  • 限制並發數:`SemaphoreSlim` 控制同時連線數,避免網路風暴
  • 取消機制:`CancellationToken` 支援使用者中途取消

現代化寫法:Task + async/await(.NET 4.5+)

public async Task ScanWithTaskAsync(string ipPrefix, int start, int end, IProgress progress)
{
    var tasks = new List();
    var semaphore = new SemaphoreSlim(50); // 限制並發 50

    for (int i = start; i <= end; i++)
    {
        string ip = $"{ipPrefix}.{i}";
        tasks.Add(Task.Run(async () =>
        {
            await semaphore.WaitAsync();
            try
            {
                IPHostEntry host = await Dns.GetHostEntryAsync(ip);
                progress.Report($"{ip} - {host.HostName} [線上]");
            }
            catch
            {
                progress.Report($"{ip} - 無回應 [離線]");
            }
            finally
            {
                semaphore.Release();
            }
        }));
    }
    await Task.WhenAll(tasks);
}

總結

多線程在網路程式設計中效果顯著,將循序等待轉為平行處理可大幅縮短總耗時。從手動管理 `Thread` 進化到 `ThreadPool`、再到 `Task/async-await`,核心思維不變:將阻塞操作卸載到背景執行緒,主執行緒專注 UI 回應。實務上建議優先使用 Task-based 非同步模式,搭配適當的並發控制,兼顧效能與資源使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved