using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace try1
{
public partial class Form1 : Form
{
volatile bool start_a = false;
volatile bool start_b = false;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (start_a == false)
{
button1.Text = "Running";
start_a = true;
Thread thread2 = new Thread(new ThreadStart(th1));
thread2.Start();
}
else
{
button1.Text = "Click to start";
start_a = false;
}
}
void th1()
{
int a=0;
while (start_a==true)
{
label1.Invoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
Thread.Sleep(50);
a++;
}
}
void th2()
{
int b = 0;
while (start_b == true)
{
label2.Invoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
Thread.Sleep(5000);
b=b+5;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (start_b == false)
{
button2.Text = "Running";
start_b = true;
Thread thread2 = new Thread(new ThreadStart(th2));
thread2.Start();
}
else
{
button2.Text = "Click to start";
start_b = false;
}
}
private void quitting(object sender, FormClosingEventArgs e)
{
start_a = false;
start_b = false;
}
}
}
回答
我認爲問題是,你正在更新上比其所創建的線程的線程上UI控件不能在一個控件調用;我想你應該看看這個:How to update the GUI from another thread in C#?或這裏:http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx一些例子比需要更復雜,但其要點是你必須從它創建的同一個線程更新控制; Control.InvokeRequired是你想要關注的。
我們需要更多關於發生錯誤的位置和時間的詳細信息。我通過查看代碼的第一個猜測是,當你試圖關閉表單時你會得到異常。退出事件處理程序將start_a
和start_b
設置爲false,但不會等到後臺線程完成後再讓表單運行任何清理代碼。您現在在後臺線程和表單清理之間存在爭用條件。這個清理代碼會釋放窗口句柄,所以當後臺線程在五秒鐘後喚醒時,可能會嘗試將文本更改回UI線程,從而導致失敗。
解決此問題最簡單的方法是讓Join()任何現場後臺線程等待它們完成,然後讓表單完成關閉。更正確和更復雜的方法是設置適當的線程同步原語(Mutex, WaitHandle, Sempahore, ...)以允許您立即通知線程暫停。
由於使用了Invoke/BeginInvoke,不幸的是加入到線程是不夠的。 UI線程可能會等待需要Invoke/BeginInvoke完成(將在UI線程上執行)的工作線程完成退出,從而導致死鎖,如我所驗證的。 – ceztko
沒有簡單的解決方案,因爲您必須與其他線程同步結束,但Invoke請求在UI線程中執行,這是應該關閉其他線程的線程!所以tUI要求t1,t2退出,但t1,t2可能需要tUI退出! :)
添加Application.DoEvents();
(讀=進程的所有調用請求)quitting
方法是這樣的:
private void quitting(object sender, FormClosingEventArgs e)
{
start_a = false;
start_b = false;
Application.DoEvents(); // NOT the solution, is not enough!!!
}
排序大多數的比賽條件,但還遠遠不夠。
爲什麼?由於這種可能,但非常不可能的,競爭條件:
t1 before queuing Invoke
~~~~~~~>
start_a = false; start_b= false; Application.DoEvents();
<~~~~~~~
t1 queue an Invoke
~~~~~~~> (very improbable but possible)
(continue trough disposing)
<~~~~~~~
queued Invoke on disposed label -> crash!
鎖定檢驗開始變量狀態並清空消息隊列應該做的伎倆的關鍵部分。你的鍛鍊:找到其他可能的競賽條件,並找到一種方法,在最壞的情況下5秒以上退出(提示:不要使用睡眠,睡眠是惡魔)。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
object _closing1;
object _closing2;
volatile bool start_a = false;
volatile bool start_b = false;
public Form1()
{
InitializeComponent();
button1.Text = "Click to start";
button2.Text = "Click to start";
_closing1 = new object();
_closing2 = new object();
}
private void button1_Click(object sender, EventArgs e)
{
if (start_a == false)
{
button1.Text = "Running";
start_a = true;
Thread thread2 = new Thread(new ThreadStart(th1));
thread2.Start();
}
else
{
button1.Text = "Click to start";
start_a = false;
}
}
void th1()
{
int a = 0;
while (true)
{
lock (_closing1)
{
if (start_a == false)
break;
label1.BeginInvoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
}
Thread.Sleep(50);
a++;
}
}
void th2()
{
int b = 0;
while (true)
{
lock (_closing2)
{
if (start_b == false)
break;
label2.BeginInvoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
}
Thread.Sleep(5000);
b = b + 5;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (start_b == false)
{
button2.Text = "Running";
start_b = true;
Thread thread2 = new Thread(new ThreadStart(th2));
thread2.Start();
}
else
{
button2.Text = "Click to start";
start_b = false;
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
lock (_closing1)
{
start_a = false;
// Clear the message queue now so access on disposed lables is possible.
// No more invokes will be queued because 1) start_a = false
// 2) t1 is out of the critical section
Application.DoEvents();
}
lock (_closing2)
{
start_b = false;
// Clear the message queue now so access on disposed lables is possible.
// No more invokes will be queued because 1) start_b = false
// 2) t2 is out of the critical section
Application.DoEvents();
}
}
}
}
- 1. 的InvokeEvent:調用或BeginInvoke可直到窗口句柄已創建
- 2. 調用或BeginInvoke可直到窗口句柄已創建
- 3. 調用或BeginInvoke不能在控件上調用,直到窗口句柄已被創建
- 4. 調用或BeginInvoke不能在控件上調用,直到窗口句柄被創建
- 5. VB.NET調用可以到窗口句柄已創建不能在一個控件調用,但手柄創建
- 6. BeginInvoke失敗,因爲窗口句柄尚未創建
- 7. 我只能關閉窗體一次,InvalidOperation異常Invoke或BeginInvoke無法在控件上調用,直到窗口句柄已被創建
- 8. 在創建窗口句柄之前,無法在控件上調用Invoke或BeginInvoke。 C#win表格
- 9. 「調用或BeginInvoke無法在控件上調用,直到窗口句柄被創建」僅在第二次表單打開時發生
- 10. Win32Exception - 錯誤創建窗口句柄?
- 11. 創建窗口句柄時出錯
- 12. 什麼時候創建窗口句柄
- 13. 錯誤創建窗口句柄
- 14. 「創建窗口句柄時出錯」
- 15. 'System.OutOfMemoryException':創建窗口句柄時出錯
- 16. 我只能關閉表單一次,InvalidOperation異常Invoke或BeginInvoke不能在控件上調用,直到窗口句柄被創建
- 17. 得到窗口的句柄
- 18. C#PInvoke從已知窗口中找到子窗口的窗口句柄
- 19. 如何找到win32異常源「創建窗口句柄錯誤」
- 20. 獲取調用窗口的窗口句柄
- 21. Winforms中的窗口句柄調試
- 22. 錯誤使用C#創建窗口句柄
- 23. 窗口句柄和窗口尺寸
- 24. 暫停,直到外部窗口句柄關閉或按下按鈕
- 25. Python pyautogui窗口句柄
- 26. 窗口句柄C#/。NET
- 27. 窗口的主句柄
- 28. 檢索Delphi窗口句柄
- 29. 驗證Win32窗口句柄
- 30. GLUT本機窗口句柄
你的問題是什麼? – bdonlan
您必須在關閉表單之前停止這些線程。 –
+1因爲問題是現實世界。但下一次明確指出你的問題。我必須編譯和測試自己才能找到它。 – ceztko