1. 程式人生 > >C# 多執行緒中更新窗體控制元件

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.privatevoid btnStart_Click(objectsender, 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.voidstartProgress() 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.delegatevoid SetControlValueCallback(Control oControl,string propName, objectpropValue); 02.privatevoid SetControlPropertyValue(Control oControl,string propName, objectpropValue) 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");
            }
        }


這樣問題就得到的完美的解決。

產生這個問題的主要原因是由於,控制元件不是執行緒安全性的,線上程中訪問非執行緒安全性的操作會被丟擲異常,提醒程式設計開發人員進行程式碼修改。

再給出兩個相關的連結