invoke委託解決“執行緒間操作無效: 從不是建立控制元件的執行緒訪問它”的問題
阿新 • • 發佈:2018-11-25
###1.問題描述
線上程中更改控制元件屬性時,編譯器報錯:“執行緒間操作無效: 從不是建立控制元件的執行緒訪問它”。檢視資料後得知這個問題的根本在於,建立新執行緒後,新執行緒跟主執行緒同步執行,這時如果新執行緒提出對控制元件進行修改,將被編譯器報錯,因為可能會出現主執行緒跟新執行緒同時對同一控制元件進行修改的情況,這樣就造成了程式執行的混亂。C#委託可以合理解決這一問題。取用委託後,被委託程式碼的執行順序將被改變以避免衝突,具體原理請自行查閱。C#提供兩種委託方法,一個是invoke,另一個是BeginInvoke。關於這兩個的區別請自行查閱,在此只介紹BeginInvoke方法。
###2.解決方法
1.使用delegate宣告委託
public delegate void MyInvoke(byte[] receiveBytes);'
MyInvoke為委託名,()裡面為要傳給代理函式的值。
2.新寫一個代理函式,來呼叫執行對控制元件的操作。
線上程裡面加入委託來呼叫代理函式重新整理控制元件。
###3.例項
因為此專案較大,所以只摘出能表示出此問題的部分。在此,將刪減無關程式碼以增加程式碼簡潔度,所以不保證貼出閹割版程式碼能夠執行。
未加委託之前的錯誤程式碼:
public mainForm()
{
//
// Windows 窗體設計器支援所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 呼叫後新增任何建構函式程式碼
//
}
/// <summary>
/// 清理所有正在使用的資源。
/// </summary>
/// <summary>
/// 應用程式的主入口點。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new mainForm());
}
Boolean flagLed1 = false;
Boolean flagLed2 = false;
Boolean flagTiltA = false;
Boolean flagTiltB = false;
Boolean flagManipA = false;
Boolean flagManipB = false;
Boolean flagThruster = false;
private void mainForm_Load(object sender, EventArgs e)
{
}
UdpClient udpClient = null;
Thread thread;
private void BUTTudpConnect_Click(object sender, EventArgs e)
{
if (BUTTudpConnect.Text == "connect")
{
udpClient = new UdpClient(Convert.ToInt32(TBLocalPort.Text));
try
{
udpClient.Connect(TBRemoteIP.Text, Convert.ToInt32(TBRemotePort.Text));
UdpSend( udpClient);
thread = new Thread(UdpListing); //這裡新建了執行緒去持續監聽UDP埠。
thread.Start(udpClient);
thread.IsBackground = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
BUTTudpConnect.Text = "disconnect";
}
else
{
thread.Abort();
BUTTudpConnect.Text = "connect";
udpClient.Close();
}
}
private void UdpListing(Object obj)
{
UdpClient udpClient;
udpClient = (UdpClient)obj;
//IPEndPoint object will allow us to read datagrams sent from any source.
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
while(true)
{
Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint);
string returnData = Encoding.ASCII.GetString(receiveBytes);
labelDHeading.Text = returnData.ToString(); //錯誤,線上程裡更改了主執行緒窗體裡的控制元件屬性。
}
}
修改了三處地方,並在修改的地方標註了序號。如下
修改後的程式碼:
public mainForm()
{
//
// Windows 窗體設計器支援所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 呼叫後新增任何建構函式程式碼
//
}
/// <summary>
/// 清理所有正在使用的資源。
/// </summary>
/// <summary>
/// 應用程式的主入口點。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new mainForm());
}
Boolean flagLed1 = false;
Boolean flagLed2 = false;
Boolean flagTiltA = false;
Boolean flagTiltB = false;
Boolean flagManipA = false;
Boolean flagManipB = false;
Boolean flagThruster = false;
//更改處1/3 宣告委託
public delegate void MyInvoke(byte[] receiveBytes);
private void mainForm_Load(object sender, EventArgs e)
{
}
UdpClient udpClient = null;
Thread thread;
private void BUTTudpConnect_Click(object sender, EventArgs e)
{
if (BUTTudpConnect.Text == "connect")
{
udpClient = new UdpClient(Convert.ToInt32(TBLocalPort.Text));
try
{
udpClient.Connect(TBRemoteIP.Text, Convert.ToInt32(TBRemotePort.Text));
UdpSend( udpClient);
thread = new Thread(UdpListing);
thread.Start(udpClient); //這裡新建了執行緒去持續監聽UDP埠。
thread.IsBackground = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
BUTTudpConnect.Text = "disconnect";
}
else
{
thread.Abort();
BUTTudpConnect.Text = "connect";
udpClient.Close();
}
}
//更改出2/3 將更改控制元件的程式碼更改為代理函式以方便委託。
private void UpdateControls(byte[] receiveBytes)
{
string returnData = Encoding.ASCII.GetString(receiveBytes);
labelDHeading.Text = returnData.ToString();
}
private void UdpListing(Object obj)
{
UdpClient udpClient;
udpClient = (UdpClient)obj;
//IPEndPoint object will allow us to read datagrams sent from any source.
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
while(true)
{
Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint);
MyInvoke mi = new MyInvoke(UpdateControls); //更改出3/3 委託
this.BeginInvoke(mi, new Object[] { receiveBytes });
}
}