如果使用多线程处理来提高 windows 窗体应用程序的性能,则你必须确保以线程安全的方式调用控件。
访问 windows 窗体控件不是本身就线程安全的。如果有两个或两个以上线程操作控件的状态,则可能迫使该控件处于不一致状态。可能出现其他与线程相关的 bug,例如争用条件和死锁。请务必确保以线程安全的方式访问控件。
1.初学者常常遇到的问题
从未使用 invoke 方法创建控件的线程调用控件是不安全的。下面是一个非线程安全的调用示例。运行时会引发 invalidoperationexception 消息,报错“从并未创建该控件的线程访问该控件 控件名称”。
// this event handler creates a thread that calls a // windows forms control in an unsafe way.private void settextunsafebtn_click( object sender,
eventargs e)
{ this.demothread =
new thread(new threadstart(this.threadprocunsafe)); this.demothread.start();
}// this method is executed on the worker thread and makes// an unsafe call on the textbox control.private void threadprocunsafe()
{ this.textbox1.text = "this text was set unsafely.";
}
2.解决方法
如需对 windows 窗体控件进行线程安全的调用。
①查询控件的 invokerequired 属性。
②若 invokerequired 返回 true,则用实际调用控件的委托来调用 invoke。
③若 invokerequired 返回 false,则请直接调用控件。
这里分同步执行委托和异步执行委托。
在以下代码示例中,在 threadprocsafe 方法中实现了线程安全的调用,该方法由后台线程执行。若 textbox 控件的 invokerequired 返回 true,则 threadprocsafe 方法创建一个 settextcallback 实例并将其传递到窗体的 invoke 方法。这导致在创建了 settext 控件的线程上调用 textbox 方法,并且在该线程上下文中直接设置 text 属性。
// this event handler creates a thread that calls a
// windows forms control in a thread-safe way.
private void settextsafebtn_click(
object sender,
eventargs e)
{ this.demothread =
new thread(new threadstart(this.threadprocsafe));
this.demothread.start();
}// this method is executed on the worker thread and makes
// a thread-safe call on the textbox control.
private void threadprocsafe()
{ this.settext("this text was set safely.");
}// this delegate enables asynchronous calls for setting
// the text property on a textbox control.delegate void settextcallback(string text);
// this method demonstrates a pattern for making thread-safe
// calls on a windows forms control.
//
// if the calling thread is different from the thread that
// created the textbox control, this method creates a
// settextcallback and calls itself asynchronously using the// invoke method.
//
// if the calling thread is the same as the thread that created
// the textbox control, the text property is set directly. private void settext(string text)
{ // invokerequired required compares the thread id of the
// calling thread to the thread id of the creating thread.
// if these threads are different, it returns true.
//this.textbox1.invokerequired will be replaced by
//this.invokerequired, if want to set many controls'
//attribute or text.
if (this.textbox1.invokerequired)// or this.invokerequired
{
settextcallback d = new settextcallback(settext);
this.invoke(d, new object[] { text });
} else
{ this.textbox1.text = text;
}
}
3.backgroundworker组件
在应用程序中实现多线程的首选方式是使用 backgroundworker 组件。 backgroundworker 组件为多线程处理使用事件驱动模型。后台线程运行你的 dowork 事件处理程序,创建了你的控件的线程运行 progresschanged 和 runworkercompleted 事件处理程序。你可以从 progresschanged 和 runworkercompleted 事件处理器中调用控件。
①创建一种方法来进行你想在后台线程中进行的工作。不要调用由此方法中的主线程所创建的控件。
②创建一种方法来报告后台工作结束后的后台工作结果。 在此方法中可以调用主线程创建的控件。
③将步骤 1 中创建的方法绑定到 dowork 实例中的 backgroundworker 事件,并将步骤 2 中创建的方法绑定到同一实例的 runworkercompleted 事件。
④若要启动后台线程,请调用 runworkerasync 实例的 backgroundworker 方法。
在以下代码示例中,dowork 事件处理程序使用 sleep 来模拟需要花费一些时间的工作。它不会调用该窗体的 textbox 控件。textbox 控件的 text 属性直接在 runworkercompleted 事件处理程序中设置。
// this backgroundworker is used to demonstrate the
// preferred way of performing asynchronous operations.private backgroundworker backgroundworker1;
// this event handler starts the form's // backgroundworker by calling runworkerasync.
//
// the text property of the textbox control is set
// when the backgroundworker raises the runworkercompleted
// event.private void settextbackgroundworkerbtn_click(
object sender,
eventargs e)
{ this.backgroundworker1.runworkerasync();
}// this event handler sets the text property of the textbox
// control. it is called on the thread that created the
// textbox control, so the call is thread-safe.
//
// backgroundworker is the preferred way to perform asynchronous
// operations.private void backgroundworker1_runworkercompleted(
object sender,
runworkercompletedeventargs e)
{ this.textbox1.text =
"this text was set safely by backgroundworker.";
}
也可通过使用 progresschanged 事件来报告后台任务的进度。如需包含该事件的示例,请参阅 backgroundworker。
以上就是 从0自学c#02--子线程访问主线程(ui线程)控件的内容。