首先我们要清楚Delegate.Invoke/BeginInvoke和Control.Invoke/BeginInvoke是不同的,他们的区别,简单理解如下:
- Delegate.Invoke:在同一个线程上同步执行。
- Delegate.BeginInvoke:在线程池线程上异步执行。
- Control.Invoke:在创建控件的基础窗口句柄的线程上执行委托。
- Control.BeginInvoke:在创建控件的基础句柄的线程上异步执行委托。
在这里,我们重点讨论的是Control中的Invoke/BeginInvoke的区别。
Invoke和BeginInvoke的执行线程
上面的表述不好理解(这也是MSDN翻译成中文的通病),下面我们通过代码,查看一下执行Invoke的线程:
private void Button1_Click(object sender, EventArgs e)
{
InvokeThreadName();
}
private void InvokeThreadName()
{
Thread.CurrentThread.Name = "UIThread";
Thread th = new Thread(CallInvoke)
{
Name = "InvokeThreadName",
IsBackground=true
};
th.Start();
}
private void CallInvoke()
{
this.Invoke(new Action(PrintCurrentThread));
}
在调试的输出界面中输出:
UIThread
因此,虽然我们通过其他线程调用窗体的的Invoke方法,但是invoke方法里面的代码依然是通过UI线程执行的。
下面我们在查看执行BeginInvoke的线程:
private void Button1_Click(object sender, EventArgs e)
{
//InvokeThreadName();
BeginInvokeThreadName();
}
private void BeginInvokeThreadName()
{
Thread.CurrentThread.Name = "UIThread";
Thread th = new Thread(CallBeginInvoke)
{
Name = "BeginInvokeThreadName"
};
th.Start();
}
private void CallBeginInvoke()
{
this.BeginInvoke(new Action(PrintCurrentThread));
}
private void PrintCurrentThread()
{
Console.WriteLine(Thread.CurrentThread.Name);
}
在调试的输出界面中输出依然是:
UIThread
也就是说BeginInvoke方法里面的代码也是通过UI线程执行的。
既然都是在UI线程中执行的,那么Invoke和BeginInvoke到底有什么区别了?
Invoke和BeginInvoke的执行顺序
我们通过代码查看一下Invoke代码的执行顺序:
private void Button1_Click(object sender, EventArgs e)
{
//InvokeThreadName();
//BeginInvokeThreadName();
TestInvoke();
}
private void TestInvoke()
{
Console.WriteLine("开始");
Thread th = new Thread(CallBeginInvoke)
{
Name = "BeginInvokeThreadName"
};
th.Start();
Console.WriteLine("结束");
}
private void TestInvokeMethod()
{
this.Invoke(new Action(() =>
{
Thread.Sleep(1000);
Console.WriteLine("这是一个耗时过程");
}));
}
在调试的输出界面中输出顺序为:
开始
这是一个耗时过程
结束
也就是说,在使用调用Invoke方法时,会阻塞UI线程,同时Invoke下面的代码需要等待Invoke内部的代码执行完之后才可以执行。
接下来我们再通过代码查看一下BeginInvoke代码的执行顺序:
private void Button1_Click(object sender, EventArgs e)
{
//InvokeThreadName();
//BeginInvokeThreadName();
//TestInvoke();
TestBeginInvoke();
}
private void TestBeginInvoke()
{
Console.WriteLine("开始");
this.BeginInvoke(new Action(() =>
{
Thread.Sleep(30000);
Console.WriteLine("这是一个耗时过程");
}));
Console.WriteLine("结束");
}
在调试的输出界面中输出顺序变为:
开始
结束
这是一个耗时过程
点击窗体上的Button按钮后,在没有输出“这是一个耗时过程”的过程中,我们无法拖动窗体的,说明在调用BeginInvoke方法时,依然会阻塞UI线程,但是它是异步执行BeginInvoke中的代码,UI线程中的代码执行顺序不受BeginInvoke的影响。
小结
- Control的Invoke和BeginInvoke的方法都是在UI线程上执行。
- Invoke方法会阻塞UI线程中Invoke方法后面代码的执行,BeginInvoke方法则不会。
- 千万不要通过Invoke和BeginInvoke方法执行一个耗时过程,否则会使界面卡死。