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

C# 模態視窗中跨執行緒更新 ProgressBar 完整解決方案

     編輯:關於C#

問題核心

Form.ShowDialog() 會阻塞呼叫執行緒(通常是 UI 執行緒),導致後續程式碼無法執行;但長時間工作又需要即時更新模態視窗上的 ProgressBar。單一執行緒無法同時「阻塞等待關閉」與「執行工作並更新 UI」。

解決架構:雙執行緒協作模式

  • 執行緒 A(UI 執行緒):啟動模態視窗 ShowDialog(),處理視窗訊息迴圈。
  • 執行緒 B(背景工作執行緒):執行實際工作,定期透過 Invoke 更新模態視窗的 ProgressBar。
  • 取消機制:模態視窗提供「取消」按鈕,設定 DialogResult = Cancel,背景執行緒輪詢該屬性決定是否提早結束。

完整實作程式碼

// Form1.cs - 主視窗
public partial class MainForm : Form
{
    private readonly ProgressForm _progressForm = new ProgressForm();

    public MainForm()
    {
        InitializeComponent();
    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        // 1. 重置進度視窗狀態
        _progressForm.Reset();

        // 2. 背景執行緒執行工作
        var workThread = new Thread(DoWork) { IsBackground = true };
        workThread.Start();

        // 3. UI 執行緒顯示模態視窗(阻塞此處直到視窗關閉)
        _progressForm.ShowDialog(this);
    }

    private void DoWork()
    {
        for (int i = 1; i <= 100; i++)
        {
            // 檢查使用者是否點擊取消
            if (_progressForm.DialogResult == DialogResult.Cancel)
                break;

            // 模擬工作耗時
            Thread.Sleep(50);

            // 跨執行緒安全更新 ProgressBar
            _progressForm.UpdateProgress(100, i);
        }

        // 工作結束,關閉模態視窗(必須在 UI 執行緒)
        _progressForm.Invoke(new Action(() => _progressForm.Close()));
    }
}

// ProgressForm.cs - 模態進度視窗
public partial class ProgressForm : Form
{
    public ProgressForm()
    {
        InitializeComponent();
        progressBar1.Minimum = 0;
        progressBar1.Maximum = 100;
        progressBar1.Value = 0;
    }

    public void Reset()
    {
        progressBar1.Value = 0;
        DialogResult = DialogResult.None; // 重置取消狀態
        btnCancel.Enabled = true;
    }

    // 供背景執行緒呼叫,內部自動 Invoke
    public void UpdateProgress(int max, int current)
    {
        if (InvokeRequired)
        {
            BeginInvoke(new Action(UpdateProgress), max, current);
            return;
        }
        progressBar1.Maximum = max;
        progressBar1.Value = Math.Min(current, max);
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        DialogResult = DialogResult.Cancel; // 通知背景執行緒停止
        btnCancel.Enabled = false;
    }
}

關鍵細節解析

細節說明
IsBackground = true主視窗關閉時自動結束背景執行緒,避免殭屍程序
BeginInvoke vs Invoke非同步不阻塞背景執行緒,適合高頻更新;若需確保更新完成再繼續用 Invoke
DialogResult 作為取消旗標模態視窗關閉時自動設定,背景執行緒只需讀取即可,無需額外鎖/旗標
工作結束主動 Close()否則模態視窗會一直開著,使用者需手動關閉
例外處理背景執行緒未捕捉例外會導致程序崩潰,務必用 try/catch 包裹並透過 Invoke 顯示錯誤

現代化改寫:async/await + Task

private async void btnStart_Click(object sender, EventArgs e)
{
    _progressForm.Reset();
    _progressForm.Show(this); // 非模態顯示,配合 await 不阻塞 UI

    try
    {
        await Task.Run(() =>
        {
            for (int i = 1; i <= 100; i++)
            {
                if (_progressForm.DialogResult == DialogResult.Cancel) break;
                Thread.Sleep(50);
                _progressForm.UpdateProgress(100, i);
            }
        });
    }
    finally
    {
        _progressForm.Close();
    }
}

注意:此版本用 Show 非模態,配合 await 達到「不阻塞 UI 且可等待完成」效果。若必須模態(禁止操作主視窗),仍需維持雙執行緒架構。

進階:IProgress<T> 模式解耦

private void DoWork(IProgress progress, CancellationToken token)
{
    for (int i = 1; i <= 100; i++)
    {
        token.ThrowIfCancellationRequested();
        Thread.Sleep(50);
        progress.Report(i);
    }
}

// 呼叫端
var progress = new Progress(v => _progressForm.UpdateProgress(100, v));
var cts = new CancellationTokenSource();
_progressForm.CancelClicked += (s, e) => cts.Cancel();

await Task.Run(() => DoWork(progress, cts.Token), cts.Token);

總結

模態視窗更新進度條的核心矛盾在於「單一 UI 執行緒無法同時阻塞與工作」。標準解法為雙執行緒協作:UI 執行緒跑 ShowDialog,背景執行緒跑工作並 Invoke 更新。本文提供傳統 Thread、現代 async/await、解耦 IProgress<T> 三種實作,涵蓋取消、例外、資源釋放等完整生命週期,可直接套用於檔案匯入、報表產生、資料同步等長時間操作場景。

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