Form.ShowDialog() 會阻塞呼叫執行緒(通常是 UI 執行緒),導致後續程式碼無法執行;但長時間工作又需要即時更新模態視窗上的 ProgressBar。單一執行緒無法同時「阻塞等待關閉」與「執行工作並更新 UI」。
ShowDialog(),處理視窗訊息迴圈。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 顯示錯誤 |
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 且可等待完成」效果。若必須模態(禁止操作主視窗),仍需維持雙執行緒架構。
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> 三種實作,涵蓋取消、例外、資源釋放等完整生命週期,可直接套用於檔案匯入、報表產生、資料同步等長時間操作場景。