C#技巧之同步与异步

发布于:2024-05-10 ⋅ 阅读:(40) ⋅ 点赞:(0)

区别

首先,同步就是程序从上往下顺序执行,要执行完当前流程,才能往下个流程去。

而异步,则是启动当前流程以后,不需要等待流程完成,立刻就去执行下一个流程。


同步示例

创建一个窗体,往窗体里面添加一个button,写入以下代码。

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再数数,然后打印启动时间和结束时间。我们看看执行结果。

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,添加以下代码。


异步示例

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使用的方法是通过委托的异步执行来实现。我们再来看看结果。

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,那么使用上面的方法,就有点难以控制。这时,我们可以使用回调函数,所谓回调函数,就是当异步执行结束以后会执行的函数,这时,只要把下个流程要执行的条件,写在当前流程的回调函数中,那么就可以实现顺序控制了。

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);

}

运行结果:

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,添加以下代码。

 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后,会显示如下结果。

要解决这种方法,就可以使用异步调用。代码如下。

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;
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到