C# 多執行緒中更新窗體控制元件
在C#中用到多執行緒處理一些功能,同時希望介面同步顯示更新,如果直接寫程式碼就會出現一些問題:
問題程式碼:
Thread ScanFileThread;
ScanFileThread = new Thread(WorkScanFileThread);
this.ScanFileThread.Start();
WorkScanFileThread執行緒體程式碼:
private void WorkScanFileThread()
{
for (nDriveIndex = 0; nDriveIndex < DriveList.Count; nDriveIndex++)
{
this.listView3.Items[nDriveIndex].SubItems[2].Text = "Scaning...";
...
//指定的操作,操作結束後設置控制元件文字為完成
...
SetListViewControlPropertyValue(this.listView3, nDriveIndex, 2, "Finished");
}
}
直接這樣呼叫,會在執行時丟擲異常,下面是異常資訊:
未處理 System.InvalidOperationException
Message="Cross-thread operation not valid: Control 'listView3' accessed from a thread other than the thread it was created on."
Source="System.Windows.Forms"
StackTrace:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.ListView.ListViewNativeItemCollection.DisplayIndexToID(Int32 displayIndex)
at System.Windows.Forms.ListView.ListViewNativeItemCollection.get_Item(Int32 displayIndex)
at System.Windows.Forms.ListView.ListViewItemCollection.get_Item(Int32 index)
at Undelete_UI.Scan.WorkScanFileThread() in D:\C# Project\Undelete_UI - Copy\Undelete_UI\Scan.cs:line 123
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
下面給出MSDN給出的解決方案:
摘取C#相關程式碼如下:
// This event handler creates a background thread that // attempts to set a Windows Forms control property // directly. private void setTextUnsafeBtn_Click (object sender, EventArgs e) { // Create a background thread and start it. this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe)); this.demoThread.Start(); // Continue in the main thread. Set a textbox value that // would be overwritten by demoThread if it succeeded. // This value will appear immediately, then two seconds // later the background thread will try to make its // change to the textbox. textBox1.Text = "Written by the main thread."; } // This method is executed on the worker thread. It attempts // to access the TextBox control directly, which is not safe. private void ThreadProcUnsafe() { // Wait two seconds to simulate some background work // being done. Thread.Sleep(2000); this.textBox1.Text = "Written unsafely by the background thread."; }
上述程式碼並不能解決這個問題,在網上搜了一下,找到了這個方法,很好用:
摘抄部分原文:
Solution
01.
private
void
btnStart_Click(
object
sender, EventArgs e)
02.
{
03.
progressBar1.Minimum = 0;
04.
progressBar1.Maximum = 100;
05.
06.
System.Threading.Thread t1 =
new
System.Threading.Thread(startProgress);
07.
t1.Start();
08.
}
09.
void
startProgress()
10.
{
11.
for
(
int
i = 0; i {
12.
SetControlPropertyValue(progressBar1,
"value"
,
i);
//This is a thread safe method
13.
System.Threading.Thread.Sleep(100);
14.
}
15.
}
Note how SetControlpropertyValue function is used above. Following is it's definition.
01.
delegate
void
SetControlValueCallback(Control oControl,
string
propName,
object
propValue);
02.
private
void
SetControlPropertyValue(Control oControl,
string
propName,
object
propValue)
03.
{
04.
if
(oControl.InvokeRequired)
05.
{
06.
SetControlValueCallback d =
new
SetControlValueCallback(SetControlPropertyValue);
07.
oControl.Invoke(d,
new
object
[] { oControl, propName, propValue });
08.
}
09.
else
10.
{
11.
Type t = oControl.GetType();
12.
PropertyInfo[] props = t.GetProperties();
13.
foreach
(PropertyInfo p
in
props)
14.
{
15.
if
(p.Name.ToUpper() == propName.ToUpper())
16.
{
17.
p.SetValue(oControl, propValue,
null
);
18.
}
19.
}
20.
}
21.
}
You can apply same solution to any windows control. All you have to do is, copySetControlValueCallback delegate and SetControlPropertyValue function from above code. For example if you want to set property of a label, useSetControlPropertyValue function.SetControlPropertyValue(Label1, "Text", i.ToString());
Make sure you supply property value in correct type. In above example Text is a string property. This is why I am converting variablei to string.
自己根據上面的程式碼對程式進行了修改,修改後的程式碼如下:
delegate void SetControlValueCallback(Control oControl, string propName, object propValue);
private void SetControlPropertyValue(Control oControl, string propName, object propValue)
{
if (oControl.InvokeRequired)
{
SetControlValueCallback d = new SetControlValueCallback(SetControlPropertyValue);
oControl.Invoke(d, new object[] { oControl, propName, propValue });
}
else
{
Type t = oControl.GetType();
PropertyInfo[] props = t.GetProperties();
foreach (PropertyInfo p in props)
{
if (p.Name.ToUpper() == propName.ToUpper())
{
p.SetValue(oControl, propValue, null);
}
}
}
}
//這是根據需要改寫的,用於給ListView指定列賦值的
delegate void SetListViewControlValueCallback(ListView oControl, int nItem, int nSubItem, string propValue);
private void SetListViewControlPropertyValue(ListView oControl, int nItem, int nSubItem, string propValue)
{
if (oControl.InvokeRequired)
{
SetListViewControlValueCallback d = new SetListViewControlValueCallback(SetListViewControlPropertyValue);
oControl.Invoke(d, new object[] { oControl,nItem,nSubItem, propValue });
}
else
{
oControl.Items[nItem].SubItems[nSubItem].Text = propValue;
}
}
private void WorkScanFileThread()
{
for (nDriveIndex = 0; nDriveIndex < DriveList.Count; nDriveIndex++)
{
SetControlPropertyValue(this.progressBar1, "value", 0);
SetListViewControlPropertyValue(this.listView3, nDriveIndex,2, "Scaning...");
//執行的功能操作
SetListViewControlPropertyValue(this.listView3, nDriveIndex, 2, "Finished");
}
}
這樣問題就得到的完美的解決。
產生這個問題的主要原因是由於,控制元件不是執行緒安全性的,線上程中訪問非執行緒安全性的操作會被丟擲異常,提醒程式設計開發人員進行程式碼修改。
再給出兩個相關的連結