程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 在多線程中挪用winform窗體控件的完成辦法

在多線程中挪用winform窗體控件的完成辦法

編輯:C#入門知識

在多線程中挪用winform窗體控件的完成辦法。本站提示廣大學習愛好者:(在多線程中挪用winform窗體控件的完成辦法)文章只能為提供參考,不一定能成為您想要的結果。以下是在多線程中挪用winform窗體控件的完成辦法正文


本文實例講述了在C#中完成多線程中挪用winform窗體控件的辦法,關於C#法式設計的進修有著很好的自創參考價值。詳細辦法以下:

起首,因為Windows窗體控件實質上不是線程平安的。是以假如有兩個或多個線程過度操作某一控件的狀況(set value),則能夠會迫使該控件進入一種紛歧致的狀況。還能夠湧現其他與線程相干的 bug,包含爭用和逝世鎖的情形。因而在調試器中運轉運用法式時,假如創立某控件的線程以外的其他線程試圖挪用該控件,則調試器會激發一個 InvalidOperationException

本文用一個很簡略的示例來說解這個成績(在窗體上放一個TextBox和一個Button,點擊Button後,在新建的線程中設置TextBox的值)

處理方法一: 封閉該異常檢測的方法來防止異常的湧現

經由測試發明此種辦法固然防止了異常的拋出,然則其實不能包管法式運轉成果的准確性 (好比多個線程同時設置TextBox1的Text時,很難估計終究TextBox1的Text是甚麼)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace winformTest
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
      Control.CheckForIllegalCrossThreadCalls = false;//這一行是症結   
    }
    

    private void button1_Click(object sender, EventArgs e)
    {
      SetTextBoxValue();
    }

    void SetTextBoxValue()
    {
      TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method1");
      ThreadStart TS = new ThreadStart(tbsv.SetText);
      Thread T = new Thread(TS);
      T.Start();
    }


    class TextBoxSetValue
    {
      private TextBox _TextBox ;
      private string _Value;

      public TextBoxSetValue(TextBox TxtBox, String Value) 
      {
        _TextBox = TxtBox;
        _Value = Value;
      }

      public void SetText() 
      {
        _TextBox.Text = _Value;
      }
    }
  }
}

處理方法二:經由過程拜托平安挪用

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace winformTest
{
  public partial class Form2 : Form
  {
    public Form2()
    {
      InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
      SetTextBoxValue();
    }    
    private delegate void CallSetTextValue();
    //經由過程拜托挪用
    void SetTextBoxValue() 
    {
      TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method2");
      if (tbsv.TextBox.InvokeRequired)
      {
        CallSetTextValue call = new CallSetTextValue(tbsv.SetText);
        tbsv.TextBox.Invoke(call);        
      }
      else
      {
        tbsv.SetText();
      }
    }
    class TextBoxSetValue
    {
      private TextBox _TextBox;
      private string _Value;
      public TextBoxSetValue(TextBox TxtBox, String Value)
      {
        _TextBox = TxtBox;
        _Value = Value;
      }
      public void SetText()
      {
        _TextBox.Text = _Value;
      }
      public TextBox TextBox {
        set { _TextBox = value; }
        get { return _TextBox; }
      }      
    }
  }
}

第三處理方法:應用BackgroundWorker控件

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace winformTest
{
  public partial class Form3 : Form
  {
    public Form3()
    {
      InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    { 
      using (BackgroundWorker bw = new BackgroundWorker())
      {
        bw.RunWorkerCompleted += SetTextBoxValue;
        bw.RunWorkerAsync();
      }
    } 
    void SetTextBoxValue(object sender, RunWorkerCompletedEventArgs e) 
    {
      TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method3");
      tbsv.SetText();
    }
    class TextBoxSetValue
    {
      private TextBox _TextBox;
      private string _Value;
      public TextBoxSetValue(TextBox TxtBox, String Value)
      {
        _TextBox = TxtBox;
        _Value = Value;
      }
      public void SetText()
      {
        _TextBox.Text = _Value;
      }
    }
  }
}

用戶不愛好反響慢的法式。在履行耗時較長的操作時,應用多線程是明智之舉,它可以進步法式 UI 的呼應速度,使得一切運轉顯得更加疾速。在 Windows 中停止多線程編程已經是 C++ 開辟人員的專屬特權,然則如今,可使用一切兼容 Microsoft .NET 的說話來編寫。

不外Windows 窗體系統構造對線程應用制訂了嚴厲的規矩。假如只是編寫單線程運用法式,則沒需要曉得這些規矩,這是由於單線程的代碼弗成能違背這些規矩。但是,一旦采取多線程,就須要懂得 Windows 窗體中最主要的一條線程規矩:除少少數的破例情形,不然都不要在它的創立線程之外的線程中應用控件的任何成員。本規矩的破例情形有文檔解釋,但如許的情形異常少。這實用於其類派生自 System.Windows.Forms.Control 的任何對象,個中簡直包含 UI 中的一切元素。一切的 UI 元素(包含表單自己)都是從 Control 類派生的對象。另外,這條規矩的成果是一個被包括的控件(如,包括在一個表單中的按鈕)必需與包括它控件位處於統一個線程中。也就是說,一個窗口中的一切控件屬於統一個 UI 線程。現實中,年夜部門 Windows 窗體運用法式終究都只要一個線程,一切 UI 運動都產生在這個線程上。這個線程平日稱為 UI 線程。這意味著您不克不及挪用用戶界面中隨意率性控件上的任何辦法,除非在該辦法的文檔解釋中指出可以挪用。該規矩的破例情形(總有文檔記載)異常少並且它們之間關系也不年夜。請留意,以下代碼長短法的:

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  myThread.Start();
}
private void RunsOnWorkerThread()
{
  label1.Text = "myThread線程挪用UI控件";
}

假如您在 .NET Framework 1.0 版本中測驗考試運轉這段代碼,或許會幸運運轉勝利,或許初看起來是如斯。這就是多線程毛病中的重要成績,即它們其實不會立刻浮現出來。乃至當湧現了一些毛病時,在第一次演示法式之前一切看起來也都很正常。但不要弄錯 — 我適才顯示的這段代碼顯著違背了規矩,而且可以預感,任何抱願望於“試運轉時優越,應當就沒有成績”的人期近將到來的調試期是會支付繁重價值的。

上面我們來看看有哪些辦法可以處理這一成績。

1、System.Windows.Forms.MethodInvoker 類型是一個體系界說的拜托,用於挪用不帶參數的辦法。

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  myThread.Start();
}
private void RunsOnWorkerThread()
{
  MethodInvoker mi = new MethodInvoker(SetControlsProp);
  BeginInvoke(mi);
}
private void SetControlsProp()
{
  label1.Text = "myThread線程挪用UI控件";
}

2、直接用System.EventHandle(可帶參數)

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  myThread.Start();
}
private void RunsOnWorkerThread()
{
  //DoSomethingSlow();
  string pList = "myThread線程挪用UI控件";
  label1.BeginInvoke(new System.EventHandler(UpdateUI), pList);
}
//直接用System.EventHandler,沒有需要自界說拜托
private void UpdateUI(object o, System.EventArgs e)
{
  //UI線程設置label1屬性
  label1.Text = o.ToString() + "勝利!";
}

3、包裝 Control.Invoke

固然第二個辦法中的代碼處理了這個成績,但它相當繁瑣。假如幫助線程願望在停止時供給更多的反應信息,而不是簡略地給出“Finished!”新聞,則 BeginInvoke過於龐雜的應用辦法會使人生畏。為了轉達其他新聞,例如“正在處置”、“一切順遂”等等,須要想法向 UpdateUI 函數傳遞一個參數。能夠還須要添加一個進度欄以進步反應才能。這麼屢次挪用 BeginInvoke 能夠招致幫助線程受該代碼安排。如許不只會形成未便,並且斟酌到幫助線程與 UI 的調和性,如許設計也欠好。對這些停止剖析以後,我們以為包裝函數可以處理這兩個成績。

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  myThread.Start();
}
private void RunsOnWorkerThread()
{
  ////DoSomethingSlow();
  for (int i = 0; i < 100; i++)
  {
 ShowProgress( Convert.ToString(i)+"%", i);
 Thread.Sleep(100);
  }
}
public void ShowProgress(string msg, int percentDone)
{
  // Wrap the parameters in some EventArgs-derived custom class:
  System.EventArgs e = new MyProgressEvents(msg, percentDone);
  object[] pList = { this, e };
  BeginInvoke(new MyProgressEventsHandler(UpdateUI), pList);
}
private delegate void MyProgressEventsHandler(object sender, MyProgressEvents e);
private void UpdateUI(object sender, MyProgressEvents e)
{
  lblStatus.Text = e.Msg;
  myProgressControl.Value = e.PercentDone;
}
public class MyProgressEvents : EventArgs
{
  public string Msg;
  public int PercentDone;
  public MyProgressEvents(string msg, int per)
  {
    Msg = msg;
    PercentDone = per;
  }
}

ShowProgress 辦法對將挪用引向准確線程的任務停止封裝。這意味著幫助線程代碼不再擔憂須要過量存眷 UI 細節,而只需按期挪用 ShowProgress 便可。

假如我供給一個設計為可從任何線程挪用的公共辦法,則完整有能夠或人會從 UI 線程挪用這個辦法。在這類情形下,沒需要挪用 BeginInvoke,由於我曾經處於准確的線程中。挪用 Invoke 完整是糟蹋時光和資本,不如直接挪用恰當的辦法。為了不這類情形,Control 類將地下一個稱為 InvokeRequired 的屬性。這是“只限 UI 線程”規矩的另外一個破例。它可從任何線程讀取,假如挪用線程是 UI 線程,則前往假,其他線程則前往真。這意味著我可以按以下方法修正包裝:

public void ShowProgress(string msg, int percentDone)
{
  if (InvokeRequired)
  {
 // As before
 //...
  }
  else
  {
 // We're already on the UI thread just
 // call straight through.
 UpdateUI(this, new MyProgressEvents(msg,PercentDone));
  }
}

線程翻開窗體的成績:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication36
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
    }
    private void button1_Click(object sender, EventArgs e)
    {
      new System.Threading.Thread(new System.Threading.ThreadStart(invokeShow)).Start();
    }
    public void invokeShow()
    {
       Form f1 = new Form();
       this.Invoke(new System.EventHandler(this.showForm), new object[] { f1, null });
    }
    public void showForm(object sender,EventArgs e)
    {
      Form f1 = sender as Form;
      f1.ShowDialog();
    }
  }
}
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved