区别
首先,同步就是程序从上往下顺序执行,要执行完当前流程,才能往下个流程去。
而异步,则是启动当前流程以后,不需要等待流程完成,立刻就去执行下一个流程。
同步示例
创建一个窗体,往窗体里面添加一个button,写入以下代码。
cs
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
method($"button1_click{i}");
}
}
private void method(string str)
{
Console.WriteLine($"{str}在线程{Thread.CurrentThread.ManagedThreadId.ToString()}启动,时间{DateTime.Now.Second.ToString()}:{DateTime.Now.Millisecond.ToString()}");
for (var i = 0; i < 1000; i++)
{
Thread.Sleep(1);
}
Console.WriteLine($"{str}线程{Thread.CurrentThread.ManagedThreadId.ToString()}结束,时间{DateTime.Now.Second.ToString()}:{DateTime.Now.Millisecond.ToString()}");
Console.WriteLine("--------------------------------------");
}
如果所示,当点击按钮以后,会五次执行method方法,而method方法就是一个从0-999再数数,然后打印启动时间和结束时间。我们看看执行结果。
cs
button1_click0在线程1启动,时间16:239
button1_click0线程1结束,时间18:229
--------------------------------------
button1_click1在线程1启动,时间18:229
button1_click1线程1结束,时间20:209
--------------------------------------
button1_click2在线程1启动,时间20:209
button1_click2线程1结束,时间22:195
--------------------------------------
button1_click3在线程1启动,时间22:195
button1_click3线程1结束,时间24:182
--------------------------------------
button1_click4在线程1启动,时间24:182
button1_click4线程1结束,时间26:169
--------------------------------------
可以看到,button_click是一一对应的,也即是说当click0启动后,要等0完成,才能启动click1,并且,这五个循环,都是在主线程1中执行,这就会导致一个问题,因为控件的创建也是在主线程1中,如果线程1被拿来执行一些长耗时的工作,那么窗体上的控件就会卡住。下面我们再看看异步示例。再创建个button2,添加以下代码。
异步示例
cs
private void button2_Click(object sender, EventArgs e)
{
Action<string> action = method;
for (int i = 0;i<5;i++)
{
action.BeginInvoke($"button2_click{i}", null, null);
}
}
private void method(string str)
{
Console.WriteLine($"{str}在线程{Thread.CurrentThread.ManagedThreadId.ToString()}启动,时间{DateTime.Now.Second.ToString()}:{DateTime.Now.Millisecond.ToString()}");
for (var i = 0; i < 1000; i++)
{
Thread.Sleep(1);
}
Console.WriteLine($"{str}线程{Thread.CurrentThread.ManagedThreadId.ToString()}结束,时间{DateTime.Now.Second.ToString()}:{DateTime.Now.Millisecond.ToString()}");
Console.WriteLine("--------------------------------------");
}
同样是执行5次method2,但button2使用的方法是通过委托的异步执行来实现。我们再来看看结果。
cs
button2_click1在线程6启动,时间15:389
button2_click4在线程9启动,时间15:389
button2_click0在线程3启动,时间15:390
button2_click2在线程7启动,时间15:389
button2_click3在线程8启动,时间15:389
button2_click3线程8结束,时间17:369
--------------------------------------
button2_click1线程6结束,时间17:372
--------------------------------------
button2_click4线程9结束,时间17:374
--------------------------------------
button2_click0线程3结束,时间17:374
--------------------------------------
button2_click2线程7结束,时间17:374
--------------------------------------
可以看到,异步执行的话是同时触发五个计数流程,然后通过五个不同的线程来分别进行,所以不仅主线程创建的控件不会卡死,另外,执行时间也比同步要快不少。
但异步会出现一个问题,就是无序,如果有些流程,要先执行完1,再执行2,那么使用上面的方法,就有点难以控制。这时,我们可以使用回调函数,所谓回调函数,就是当异步执行结束以后会执行的函数,这时,只要把下个流程要执行的条件,写在当前流程的回调函数中,那么就可以实现顺序控制了。
cs
private void button2_Click(object sender, EventArgs e)
{
Action<string> action = method;
AsyncCallback asyncCallback = method2;
for (int i = 0;i<5;i++)
{
action.BeginInvoke($"button2_click{i}", asyncCallback, null);
}
}
private void method(string str)
{
Console.WriteLine($"{str}在线程{Thread.CurrentThread.ManagedThreadId.ToString()}启动,时间{DateTime.Now.Second.ToString()}:{DateTime.Now.Millisecond.ToString()}");
for (var i = 0; i < 1000; i++)
{
Thread.Sleep(1);
}
Console.WriteLine($"{str}线程{Thread.CurrentThread.ManagedThreadId.ToString()}结束,时间{DateTime.Now.Second.ToString()}:{DateTime.Now.Millisecond.ToString()}");
Console.WriteLine("--------------------------------------");
}
private void method2(IAsyncResult ia)
{
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}执行完成?"+ia.IsCompleted);
}
运行结果:
cs
button2_click0在线程3启动,时间48:919
button2_click1在线程8启动,时间48:919
button2_click2在线程9启动,时间48:919
button2_click3在线程10启动,时间48:921
button2_click4在线程4启动,时间48:922
button2_click0线程3结束,时间50:894
--------------------------------------
线程3执行完成?True
button2_click2线程9结束,时间50:897
--------------------------------------
button2_click1线程8结束,时间50:897
--------------------------------------
线程8执行完成?True
线程9执行完成?True
button2_click3线程10结束,时间50:897
--------------------------------------
线程10执行完成?True
button2_click4线程4结束,时间50:897
--------------------------------------
线程4执行完成?True
可以看到,每个线程执行完以后,都会有反馈,我们可以在反馈函数中写逻辑,这里就不详细叙述了。
控件不在创建线程中被调用
如果控件不在创建线程中被调用,会报错。
创建一个button对象button3和一个label对象lbl,添加以下代码。
cs
private void button3_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(Method3);
t1.Start();
}
private void Method3()
{
for (var i = 0; i < 10; i++)
{
Thread.Sleep(1000);
ChangeLabel(label1, i.ToString());
}
}
private void ChangeLabel(Label lbl,string str)
{
lbl.Text = str;
}
如上,点击button3以后,会启动一个分线程,然后在分线程中执行Method3方法,而Method3方法的操作是,每隔一秒,就改变lbl的text属性。当我们按下button3后,会显示如下结果。
要解决这种方法,就可以使用异步调用。代码如下。
cs
private delegate void MyDel(Label lbl,string str);
private void button3_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(Method3);
t1.Start();
}
private void Method3()
{
for (var i = 0; i < 10; i++)
{
Thread.Sleep(1000);
ChangeLabel(label1, i.ToString());
}
}
private void ChangeLabel(Label lbl,string str)
{
if (lbl.InvokeRequired) //如果lbl控件被创建其线程以外的线程调用,那么InvokeRequire为true
{
MyDel myDel = ChangeLabel;
lbl.BeginInvoke(myDel, new object[] { label1, str }); //启动异步调用
}
else
{
lbl.Text = str;
}
}