事件(Event)
概念
事件是对象在外界第三方因素影响下发生的事情,而对外提供的一种消息机制
事件的两个参与者
- 发送者(Sender):对象本身,当本身状态发生变化时,触发事件,并通知事件的接收者
- 接收者(Receiver):用来处理事件的,在事件发送者触发一个事件后,会自动执行的内容
事件和委托
相同点:
事件对象本质就是一个私有的委托对象,以及公有的方法,add(+=)和remove(-=)
不同点:
- +=方法是,实际上是调用add方法对委托进行添加。
- 委托对象私有以后,无法直接从外部赋值(内部可以赋值)。
- 好处:能够避免用户直接将对象事件清除,比如微软给内部事件,只允许通过add或者remove进行注册和移除事件,而不能清除掉。这样做能够保护委托。理解事件是对委托的封装
异步编程
同步方法
程序运行的时候,在调用其他方法的时候,会等待被调用的方法按顺序执行完,才会继续执行。非常符合开发思维,有序执行
异步方法
在程序调用异步方法的时候,主程序不会等待方法执行完,而是主程序调用异步方法后直接继续运行,而异步方法会启动一个新线程来完成方法的计算。
异步编程
委托的异步调用
-
先根据比较耗时的功能方法声明对应的委托
-
给委托赋值,赋与这个耗时的功能函数
-
采用委托的BeginInvoke方法进行异步调用委托
Invoke方法:类似于默认的方法调用,相当于直接使用()调用委托
BeginInvoke方法:指采取异步调用委托 -
给BeginInvoke方法创建回调函数
回调函数:回调函数是指,当前的正在执行的操作完成之后,立刻回调用的一个函数是回调函数
BeginInvoke方法参数组成:
- 第一组参数:委托所指向的方法对应的参数
- AsyncCallback参数:与当前委托对应的回调函数
- object参数:假如回调函数需要参数则由object参数提供
BeginInvoke方法返回值为IAsyncResult,而委托的回调函数必须有一个参数为IAsyncResult类型
-
通过EndInvoke获取对应委托的BeginInvoke的执行结果
-
因为BeginInvoke相当于重新创建的线程进行异步调用方法函数,所以回调函数也是异步的线程的执行的,那么异步线程和程序主线程相当于阳关道和独木桥,
因此如果使用Winform或WPF中的控件要考虑清楚:UI中的控件等都是Winform或WPF主线程中创建的,而异步线程无法直接使用
设置CheckForIllegalCrossThreadCalls =false,这个属性是用来设置Winform或者WPF中的主线程不在做控件的跨线程检测
public partial class Form1 : Form
{
public Form1()
{
CheckForIllegalCrossThreadCalls = false;
InitializeComponent();
func1 = Fun1;
}
Func<int,int, int> func1;
private void button1_Click(object sender, EventArgs e)
{
IAsyncResult result = func1.BeginInvoke(2,10,new AsyncCallback(CallBackFunc),null);
lbl2.Text =Fun2(10).ToString();
}
void CallBackFunc(IAsyncResult result)
{
this.lbl1.Text= func1.EndInvoke(result).ToString();
}
int Fun1(int num,int num2)
{
System.Threading.Thread.Sleep(5000);//等待5秒钟
return num * num;
}
int Fun2(int num)
{
return num * num;
}
}
异步编程总结
- 异步编程是建立在委托基础上的一种编程方法
- 异步调用的每个方法都是独立的线程执行。因此。本质上就是一种多线程程序,也可以说是一种简化版本的多线程技术
- 比较适合在后台运行较为耗时的简单任务,并且任务要求相互独立,任务中不应该有代码直接访问可视化控件
- 如果后台任务要求必须按照特定顺序执行,或者必须访问公共资源,则异步编程不适合,而直接使用多线程技术
多线程的异步
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
int a = 0;
//多线程编程
//委托:ParameterizedThreadStart
Thread objThread1 = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
a += i;
//控件的属性:作用是用来判断当前控件所在的使用环境是创建这个控件的线程(false)还是其他线程(true)
if (label1.InvokeRequired)
{
//控件的Invoke方法:方法中的第一个参数返回值为void的委托,第二个参数是给委托对应方法传递的参数
label1.Invoke(new Action<string>(s =>
{
label1.Text = $"【{s}】";
}), a.ToString());
}
Thread.Sleep(500);
}
});
objThread1.Start();
}
private void button2_Click(object sender, EventArgs e)
{
int a = 0;
Thread objThread02 = new Thread(() =>
{
for (int i = 0; i < 100; i++)
{
a += i;
if (label2.InvokeRequired)
{
label2.Invoke(new Action<int>(s =>
{
label2.Text = $"【{s}】";
}), a);
System.Threading.Thread.Sleep(200);
}
}
});
objThread02.Start();
}
}
如果创建的子线程未执行完成,当主线程关闭时,这时候会抛异常
这个错误的原因是:当主线程关闭时主线程中的labal2对象也跟着被释放了,而子线程中还依然在使用label2对象,子线程使用时发现label2没了,因此抛了这样一个异常
如果按照程序设计来分析:主线程都关闭了,对应的其下的子线程都要跟着关闭。
新的问题:主线程关闭,子线程如果未完成则没有关闭
解决方法:将程序中的子线程设置为后台线程则只要前台线程都关闭了,则后台线程自动关闭
objThread1.IsBackground = true;
后台线程
指的是程序中的一些功能不需要和一般线程同时执行,或者这些功能对于UI界面中显示方面要求不高,可以设置为后台线程
例如:VS工具中的,编译检测功能就是后台线程,当VS启动起来,除过前台线程给我们展示编辑菜单等界面,同时后台运行了检测程序
多线程
概念
- 进程:每一个独立的程序都是进程。操作系统有《进程管理模块》,管理同时运行的多个程序
- 进程和线程:一个进程可以有若干个线程
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
句柄(Handle)
句柄是一个标识符,用于代表操作系统中的各种资源,比如各种窗体、GDI绘图对象、进程和线程对象、文件等都用句柄
句柄使用
- 即使进程退出,很多时候操作系统仍然保持进程句柄,操作系统句柄有限,使用用需要节省,比如打开文件后,使用完毕要及时关闭
- 句柄可以通过Process对象的Handle属性访问,比如进程退出的代码,时间等
总结:拥有图形界面的程序都有一个主窗体,这个窗体也对应有一个句柄。当窗体关闭时进程也关闭。主窗体一般由主线程负责创建