c# - 当UI中的事件被另一个线程中的对象触发而不调用跨线程操作异常时,如何处理它?
问题描述
我正在通过线程设计模式并试图解决一个小型实践项目。我得到一个 [InvalidOperationException: 'Cross-thread operation not valid: Control 'txtEventLogger' 从创建它的线程以外的线程访问。'] 当我的 UI 中的事件由另一个线程中的对象触发时调用方法在我的 UI 事件处理程序中,在文本框中记录一些事件。我怎样才能使这个线程安全?还是在使用线程时我使用了错误的设计模式来处理对象事件?
我的 GUI WinForm 有 3 个文本框和 1 个按钮:
- txtAmountOfMessages
- txtMessageToSend
- txtEventLogger
- btn开始
我有一个名为 Relay 的类,以及 EventArgs CustomEventArgs 派生的另一个类,用于事件处理。
public class Relay
{
//CustomEventArgs, derived EventArgs class to hold object index and a string message
public delegate void OnSend(object sender, CustomEventArgs e);
public event OnSend Sent;
public int index;
public int Index {get => index; set => index = value; }
public void Send(string UserMessage)
{
//TODO: Meanwhile trigger event for testing
//EventArgs class not shown, but it takes:
//CustomEventArgs Constructor CustomEventArgs(int index, string message)
Sent(this, new CustomEventArgs(this.index, UserMassage.Length.ToString()));
}
}
我的 WinForm 代码:
public partial class Form1 : Form
{
private void btnStart_Click(object sender, EventArgs e)
{
// create a couple threads
for (int i = 1; i < 3; i++)
{ // txtAmountOfMessages = 3
new Thread(() => CreateRelaysAndSendMessage(Convert.ToInt32(txtAmountOfMessages.Text), txtMessageToSend.Text)).Start();
}
}
private void CreateRelaysAndSendMessage(int AmountOfMessages, string Message)
{
List<Relay> relayList = new List<Relay>();
// Create 5 objects of Relay class, index, and subscribe to ui eventhandler
for (int i = 0; i <= 4; i++)
{
relayList.Add(new Relay());
relayList[i].Index = i;
relayList[i].Sent += RelaySent;
}
// For each object, call .Send, 3 times (from txtAmountOfMessages value)
foreach(Relay myRelay in relayList)
{
for (int j = 1; j <= AmountOfMessages; j++)
{
myRelay.Send(Message);
}
}
}
private void RelaySent(object sender, CustomEventArgs e)
{
// Exception handling error here
Log("Relay number " + e.Index.ToString() + " sent msg " + e.Message);
}
public void Log(string Message)
{
// Exception handling error here
//System.InvalidOperationException: 'Cross-thread operation not valid: Control 'txtEventLogger' accessed from a thread other than the thread it was created on.'
txtEventLogger.AppendText(Message + Environment.NewLine);
}
}
解决方案
对于其他人来说,这有一个相对直接的答案。
因为您正试图访问不同线程上的控件,所以您只需通过调用 BeginInvoke 将它们编组回来。这实际上很容易!
改变这个:
txtEventLogger.AppendText(Message + Environment.NewLine);
对此:
txtEventLogger.BeginInvoke((Action)(() => txtEventLogger.AppendText(Message + Environment.NewLine));
Begin Invoke 只是告诉 .NET 你想在 UI 线程上调用它(你应该做什么,以及错误试图告诉你什么)。
与代表一起工作可能会很痛苦,但如果您想在 Windows 窗体中进行多线程工作,这是生活的一部分。其他选项是:
- 使用 Windows 调度程序(搜索 System.Windows.Threading.Dispatcher),尽管这与在您的控件上调用 BeginInvoke 本质上是一样的。
- 使用具有用于编组调用的帮助程序的线程库。通常,这些允许您仅使用属性来装饰方法,以说“嘿,我希望它在 UI 线程上运行”,其余的由它完成(更多信息在这里https://dotnetcoretutorials.com/2020/12/10/使用 postsharp-threading/ 简化多线程场景)
推荐阅读
- sql - VBA SQL:From 子句中的语法错误,使用密码进行双重内连接
- uipath - **Uipath - 如何通过全文搜索对象**
- youtube - 如何从特定类别的播放列表中检索视频
- excel - 根据多个条件对 Excel 中的两行求和
- html - 如何使用css3关键帧动画为序列中的列表项设置动画
- c# - 多个控制器匹配 URL WebAPI
- java - ControlsFX:确保 PopOver 箭头始终指向正确的位置
- ios - Swift 如何使用不同的文件名将图像上传到 firebase?
- java - Jasypt 加密 - 加密时删除斜线
- cypress - 如何使用赛普拉斯获取元素类型 =“电子邮件”?