如何取消等待Background Worker的线程?C# WinForms开发疑问
解决WinForms中无法取消等待线程的问题
我看了你的代码,核心问题出在两个地方:一是你的等待线程(waitingMethod)没有监听取消信号,二是BackgroundWorker的取消逻辑也没正确落实。咱们一步步来修复:
关键问题分析
- 你的
_shouldStop变量没有被waitingMethod的循环检查,所以点击取消按钮后,线程还会一直循环等待。 calcFibo里的BackgroundWorker取消逻辑写得不对,递归过程中没有持续检查CancellationPending,导致BackgroundWorker无法真正终止计算。- 来回切换按钮的
Click事件容易出错,其实可以在同一个事件里根据按钮状态或文本判断执行逻辑。
解决方案步骤
1. 确保取消标志的线程可见性
把_shouldStop标记为volatile,保证多线程之间的变量可见性(避免线程缓存旧值导致取消信号不生效):
private volatile bool _shouldStop = false;
2. 修复等待线程的取消逻辑
在waitingMethod的循环里加入对_shouldStop的检查,一旦触发就退出线程,并更新对应UI状态:
public void waitingMethod() { while (arrFibo[eingabe] == 0 && !_shouldStop) { Thread.Sleep(500); Console.WriteLine("Thread sleeping " + arrFibo[eingabe]); } if (!_shouldStop) { // 计算完成后更新结果 labelErgebnis.Invoke(new Action(() => labelErgebnis.Text = arrFibo[eingabe].ToString())); } else { // 取消后提示用户 labelErgebnis.Invoke(new Action(() => labelErgebnis.Text = "计算已取消")); } // 恢复按钮初始状态并重置取消标志 buttonStartStop.Invoke(new Action(() => buttonStartStop.Text = "Berechnen")); _shouldStop = false; }
3. 正确实现BackgroundWorker的取消逻辑
修改calcFibo方法,在递归的每一步都检查CancellationPending,一旦检测到取消就立即终止递归:
public long calcFibo(BackgroundWorker bw, int n) { // 先检查是否需要取消 if (bw.CancellationPending) { return -1; // 返回标记值表示取消 } if (n == 0) { return 0; } else if (n == 1) { return 1; } else { long left = calcFibo(bw, n - 1); if (bw.CancellationPending) return -1; long right = calcFibo(bw, n - 2); if (bw.CancellationPending) return -1; long a = left + right; arrFibo[n] = a; return a; } }
4. 简化按钮事件逻辑(更优雅)
不用来回绑定Click事件,直接在同一个事件里判断按钮当前文本,逻辑更清晰:
private void buttonStartStop_Click(object sender, EventArgs e) { if (buttonStartStop.Text == "Abbrechen") { // 执行取消操作 _shouldStop = true; if (backgroundWorker1.IsBusy) { backgroundWorker1.CancelAsync(); } buttonStartStop.Text = "Berechnen"; labelErgebnis.Text = "计算已取消"; return; } // 原有的启动逻辑 if (backgroundWorker1.IsBusy || calculationComplete) { try { eingabe = int.Parse(textBoxEingabe.Text); buttonStartStop.Text = "Abbrechen"; labelErgebnis.Text = ""; // 增加输入范围校验 if (eingabe < 0 || eingabe >= arrFibo.Length) { MessageBox.Show("请输入0到91之间的数字", "Eingabefehler"); buttonStartStop.Text = "Berechnen"; return; } if (arrFibo[eingabe] == 0 && eingabe != 0) { labelErgebnis.Text = "Calculating..."; Thread t1 = new Thread(waitingMethod); t1.Start(); } else { labelErgebnis.Text = arrFibo[eingabe].ToString(); buttonStartStop.Text = "Berechnen"; } } catch (FormatException) { MessageBox.Show("Ungültige Eingabe. Nur Zahlen eingeben", "Eingabefehler"); buttonStartStop.Text = "Berechnen"; } } }
这样就可以删掉原来的buttonStartStop_Click2方法,代码更简洁易维护。
修改后的完整代码
using System; using System.ComponentModel; using System.Threading; using System.Windows.Forms; namespace Einsendeaufgabe_GPI13_4 { public partial class Form1 : Form { int eingabe; long[] arrFibo; bool calculationComplete = false; private volatile bool _shouldStop = false; public Form1() { InitializeComponent(); backgroundWorker1.RunWorkerAsync(); // Start BackGroundworker } // 统一的按钮点击事件 private void buttonStartStop_Click(object sender, EventArgs e) { if (buttonStartStop.Text == "Abbrechen") { // 取消操作 _shouldStop = true; if (backgroundWorker1.IsBusy) { backgroundWorker1.CancelAsync(); } buttonStartStop.Text = "Berechnen"; labelErgebnis.Text = "计算已取消"; return; } if (backgroundWorker1.IsBusy || calculationComplete) { try { eingabe = int.Parse(textBoxEingabe.Text); buttonStartStop.Text = "Abbrechen"; labelErgebnis.Text = ""; // 输入范围校验 if (eingabe < 0 || eingabe >= arrFibo.Length) { MessageBox.Show("请输入0到91之间的数字", "Eingabefehler"); buttonStartStop.Text = "Berechnen"; return; } if (arrFibo[eingabe] == 0 && eingabe != 0) { labelErgebnis.Text = "Calculating..."; Thread t1 = new Thread(waitingMethod); t1.Start(); } else { labelErgebnis.Text = arrFibo[eingabe].ToString(); buttonStartStop.Text = "Berechnen"; } } catch (FormatException) { MessageBox.Show("Ungültige Eingabe. Nur Zahlen eingeben", "Eingabefehler"); buttonStartStop.Text = "Berechnen"; } } } // 等待方法,支持取消 public void waitingMethod() { while (arrFibo[eingabe] == 0 && !_shouldStop) { Thread.Sleep(500); Console.WriteLine("Thread sleeping " + arrFibo[eingabe]); } if (!_shouldStop) { labelErgebnis.Invoke(new Action(() => labelErgebnis.Text = arrFibo[eingabe].ToString())); } else { labelErgebnis.Invoke(new Action(() => labelErgebnis.Text = "计算已取消")); } buttonStartStop.Invoke(new Action(() => buttonStartStop.Text = "Berechnen")); _shouldStop = false; // 重置取消标志 } // 斐波那契计算方法,支持取消 public long calcFibo(BackgroundWorker bw, int n) { if (bw.CancellationPending) { return -1; } if (n == 0) { return 0; } else if (n == 1) { return 1; } else { long left = calcFibo(bw, n - 1); if (bw.CancellationPending) return -1; long right = calcFibo(bw, n - 2); if (bw.CancellationPending) return -1; long a = left + right; arrFibo[n] = a; return a; } } // BackgroundWorker 执行计算 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bw = sender as BackgroundWorker; arrFibo = new long[92]; arrFibo[0] = 0; arrFibo[1] = 1; calcFibo(bw, 91); if (bw.CancellationPending) { e.Cancel = true; } } // BackgroundWorker 完成回调 private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled) { Console.WriteLine("Cancelled"); } else { calculationComplete = true; } } } }
额外说明
- 在修改WinForms控件时,必须使用
Invoke,因为控件只能在创建它们的UI线程中访问。 - 递归计算斐波那契数效率很低,如果你需要更高效的计算,可以改成迭代方式,取消逻辑也会更容易处理。
内容的提问来源于stack exchange,提问作者Mario S
相关产品推荐
相关产品推荐

