原因
程序运行时会生成一个主线程,主线程用于处理控件生成、界面渲染、事件响应等操作,简单理解就是窗体里的控件是属于主线程的。
如果在多线程里操作主线程的控件,主线程会拒绝并且抛出报错,解决办法有两个:
- 在Form_Load方法中添加Control.CheckForIllegalCrossThreadCalls=false;
- 利用委托(Delegate)调用函数来操作主线程的控件;
方法一:取消对跨线程访问
/* 当打开窗体时运行 */
private void Form_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false; //取消对跨线程访问的检查
}
使用该语句会取消对跨线程访问的检查,不捕获错误调用线程后产生的报错。但是该方法只是简单的将错误提示禁用了,仍然存在跨线程调用控件的问题,可能造成两个线程同时或者循环改变该控件的状态导致线程死锁。
方法二:利用委托调用函数
public static Action<string> ShowMsgEventHandler { get; set; } //声明委托类型
/* 委托函数 */
private void ShowMsgEvent(string str)
{
showMsgTextBox.AppendText(str + "\r\n");
}
void ShowMsg(string str)
{
this.ShowMsgEventHandler = (string msg) => { ShowMsgEvent(msg); };
ShowMsgEventHandler?.Invoke(str);
}
委托机制可以划分为以下三步:
- 第一步:利用delegate, Action, EventHandler等关键字声明委托类型;
- 第二步:根据需求创建委托函数,例如方法二中的创建带参数的无返回值的委托函数;
- 第三步:初始化委托类型指向委托函数。
因此,用使用匿名方法来委托来操作主线程的控件防止调用线程产生报错。
void ShowMsg(string str)
{
this.Invoke(new Action(delegate
{
showMsgTextBox.AppendText(str + "\r\n");
}));
}
最后,Lambda表达式化简委托来操作主线程的控件防止调用线程产生报错。
void ShowMsg(string str)
{
this.Invoke(new Action(()=>
{
showMsgTextBox.AppendText(str + "\r\n");
}));
}
在调用ShowMsg函数时会利用控件的父类Control的Invoke方法:
Invoke方法中的参数是一个委托,利用Lambda表达式创建一个委托来修改TextBox控件中的Text属性,此时不会产生报错。
委托
委托是对某个方法的引用的一种引用类型变量,用C++的话来说委托就是一个类型安全的、面向对象的函数指针,可以认为委托是一个指向一个或多个函数的对象。
委托是一种类型,使用委托可以类比于使用类,任何可以声明类的地方都可以声明委托,大致流程如下图所示:
使用委托流程
第一步:声明一个委托类型,以下是声明Delegate委托的方式:
delegate void Delegate_type(); //无参数无返回值委托
delegate void Delegate_type(parameter_list); //有参数无返回值委托
delegate < return type> Delegate_type(parameter_list); //有参数有返回值委托
delegate < return type> Delegate_type(); //无参数有返回的委托
第二步:使用该委托类型声明一个委托变量。
Delegate_type Delegate_variable;
第三步:创建委托类型的对象并赋值给委托变量,这个过程被称为实例化,委托是恒定的,一旦被实例化以后就不能再改变了,有以下三种常用来实例化委托的方式:
- 通过关键字new来实例化;
- 直接赋值一个函数名或Lambda表达式或匿名委托;
委托对象包括指向某个函数的引用,这个函数和第一步定义的类型返回类型一致,例如定义了无参数无返回值的委托就只能引用无参数无返回值的函数,这个被当作参数用来传递的函数被称为委托函数。
Delegate_variable = new Delegate_type(method); //通过new来实例化
Delegate_variable = method; //直接赋值一个函数名
Delegate_variable = () =>{ method() } //无参数无返回值的委托函数
Delegate_variable = (parameter_list) =>{ return method(parameter_list) } //有参数有返回值的委托函数
Delegate_variable = (parameter_list) =>{ method(parameter_list) } //有参数无返回值的委托函数
Delegate_variable = () =>{ return method() } //无参数有返回值的委托函数
第四步:(非必选)为委托对象增加/删去其他函数,这些委托函数的集合也叫调用列表。
Delegate_variable += new Delegate_type(method2);
Delegate_variable -= new Delegate_type(method1);
第五步:调用委托,其包含的每一个函数都会被执行,按顺执行调用列表中的所有函数,有以下两种常用来调用委托的方式:
Delegate_variable(variable1, … ,variableN);
Delegate_variable?.Invoke(variable1, … ,variableN));
委托类型
创建委托主要有以下三种方法:
delegate委托
至少0个参数,至多32个参数,可以无返回值,可以指定返回值类型,以下声明了有参数有返回值的delegate委托,调用委托时打印返回值。
public delegate string MergeStringDelegateHandler(string str1, string str2); //声明委托类型
/* 委托函数 */
static string MergeString(string str1, string str2) //委托函数
{
string strMerge;
strMerge = str1 + str2;
return strMerge;
}
static void Main(string[] args)
{
/* Delegate委托 */
//MergeStringEventHandler mergeStringEvent = new MergeStringEventHandler(MergeString); //创建委托对象并实例化委托
/// 委托类型 委托变量 = new 委托类型(委托函数)
MergeStringEventHandler mergeStringEvent = MergeString;
//MergeStringEventHandler mergeStringEvent = (string str1, string str2) => { return MergeString(str1, str2); }; //Lambda表达式实例化委托
/// Lambda表达式是匿名方法的化简:委托类型 委托变量 = (参数列表) => {委托函数},其中委托函数可以是一个代码块
Console.WriteLine(mergeStringEvent("Delegate","委托")); //调用委托指向MergeString(str1,str2)
/* 等价于Console.WriteLine(mergeStringEvent?.Invoke("Delegate", "委托"));
{ //等价于以下代码块
if (mergeStringEvent != null)
{
Console.WriteLine(mergeStringEvent("Delegate","委托")); //调用委托指向MergeString(str1,str2)
}
else //Action委托未实例化则打印消息
{
Console.WriteLine("无Delegate委托");
}
}
*/
}
Action委托
至少0个参数,无返回值的泛型委托,以下声明了有参数无返回值的Action委托,调用委托时打印参数。
public static Action<string> AddMsgEventHandler; //声明委托类型
/* 委托函数 */
static void AddMsgEvent(string msg)
{
Console.WriteLine(msg);
}
static void Main(string[] args)
{
/* Action委托 */
AddMsgEventHandler = (string msg) => { AddMsg(msg); }; //Lambda表达式实例化委托
AddMsgEventHandler?.Invoke("Action委托"); //调用委托指向AddMsg(mgs)
/* 等价于以下代码块
{
if (AddMsgEventHandler != null)
{
AddMsgEventHandler("Action委托"); //调用委托指向AddMsg(msg)
}
else //Action委托未实例化则打印消息
{
Console.WriteLine("无Action委托");
}
}
*/
}
注意,此处的Invoke方法区别于控件的父类Control的Invoke方法,此处的Invoke方法是委托中的。
EventHandler委托
在说EventHandler委托之前不得不提Event事件,事件在很多方面与委托类似且事件中包含有一个私有的委托,换句话说事件中有被封装的委托,事件就像是专门用于某些特殊用途的简单委托,实际上事件并不等于委托。
发布器订阅器模式可以实现当某个“你感兴趣的”Event事件发生时,发布器通知订阅器该事件发生了。C#提供了一个EventHandler委托类型的标准模式用于完成事件的异步处理,也就是说在程序运行时,特定事件发生时程序需要处理该事件后才会继续未完成的工作,类似于单片微机中的中断机制(Interrupt)。以下展示当发布器中的JobEventHandler事件触发时,订阅器收到发布器传递过来的字符串后,打印该字符串。
/// <summary>
/// 自定义的用于传递的数据的类
/// </summary>
public class JobEventArgs : EventArgs
{
public string msg { get; set; } //存储字符串
}
/// <summary>
/// 发布器:发生某个事件时,通知订阅者该事件发生了
/// </summary>
class PubJob
{
public event EventHandler<JobEventArgs> JobEventHandler; //声明EventHandler委托
/// <summary>
/// 调用EventHandler委托的函数,表明该事件发生了,声明Event的类内函数才能调用委托
/// </summary>
public void JobEventInvoke(string str)
{
JobEventArgs args = new JobEventArgs(); //实例化自定义传递数据的类
args.msg = str; //传递参数str
JobEventHandler(this,args); //调用委托指向DoJobs()
//JobEvent?.Invoke(this,args);
/*
{ //等价于以下代码块
if (JobEventHandler != null)
{
JobEventHandler(this, args); //调用EventHandler委托
}
else //EventHandler委托未注册则打印消息
{
Console.WriteLine("无EventHandler委托");
}
}
*/
}
}
/// <summary>
/// 订阅器:订阅某个事件,在该事件发生时得到通知
/// </summary>
class SubJob
{
public SubJob(PubJob pubJob)
{
pubJob.JobEventHandler += DoJobs; //实例化委托,相当于订阅了发布器
}
/// <summary>
/// 委托函数(事件处理程序),在声明订阅器时会自动添加该处理程序至事件中
/// </summary>
public void DoJobs(object source,JobEventArgs e)
{
if (e != null)
{
Console.WriteLine(e.msg); //事件发生后,订阅器打印从发布器传递过来的消息,表明订阅器知道事件发生了
}
}
}
static void Main(string[] args)
{
/* EventHandler委托 */
PubJob pub = new PubJob(); //实例化发布器类
SubJob sub = new SubJob(pub); //实例化订阅器类
pub.JobEventInvoke("EventHandler委托"); //发布器事件发生
}