• Thread,ThreadPool,Task (异步调用 async await )


    程序中经常用到的同步和异步方法

    同步:当一个方法被调用以后,调用者需等待该方法执行完毕以后方能再次调用

    异步:当调用者调用一个方法时,会分配一个线程去处理方法内部业务逻辑,当在有其他调用者调用时,调用者不必等该方法执行是否完毕,就可调用

    同步异步调用的应用场景

    同步: 在需要执行方法需要立即出结果的,或者被调用者调用等结果返回值才能进行下一步操作的,都需要同步执行

    异步:在不需要立即响应结果的,被执行的程序中基本没有关联的场景下。好处是非阻塞,因此可以把一些不需要立即使用结果,较耗时的任务设计为异步执行

    可以提升程序运行效率。net4.0在ThreadPool的基础上推出了Task类,微软也极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;

    net5.0推出了async/await,让异步编程更为方便。

    本文主要介绍Task、asyncawait相关内容

    Task简介

    Task是在ThreadPool的基础上推出的,ThreadPool中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕线程不会被销毁,而是被线程池回收供后续任务使用。当线程池中所有的线程都在忙碌时。又有新的任务需要处理时,线程池才会新建一个线程来处理该任务。如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后在执行。线程池能减少线程的创建,节省开销,举例说明ThreadPool

            static void Main(string[] args)
            {
                ThreadPoolTest();
                Console.ReadKey();
            }
            public static void ThreadPoolTest()
            {
                for (int i = 0; i < 10; i++)
                {
                    ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
                    {
                        Console.WriteLine($"第{obj}个执行任务");
                    }), i);
                }
            }



    调用以后执行结果

     从结果可以看出ThreadPool相对于Thread来说可以减少线程的创建,有效减少系统开销;但是ThreadPool不能控制线程的执行顺序,我们也不能获取线程池

    内线程取消/异常/完成的通知,即我们不能有效监控和控制线程池中的线程,基于此我们来研究一下Task

    Task创建和运行

    net4.0在ThreadPool的基础上推出了Task,Task拥有线程池的优点,同时也解决了使用线程池不易控制的弊端

    创建并运行Task,有三种方式方法:

            static void Main(string[] args)
            {
                CreatTaskMethod();
                Console.ReadKey();
            }public static void CreatTaskMethod()
            {
                //方式一:
                Task task = new Task(()=> {
                    Thread.Sleep(100);
                    Console.WriteLine($"Task第一种创建方式:通过实例化一个Task,然后通过Start方法启动,当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
                });
                task.Start();
                //方式二:
                Task task2 = Task.Factory.StartNew(() => {
                    Thread.Sleep(100);
                    Console.WriteLine($"Task第二种创建方式:直接创建并启动Task,当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
                });
                //方式三:
                Task task3 = Task.Run(()=> {
                    Thread.Sleep(100);
                    Console.WriteLine($"Task第三种创建方式:将任务放在线程池队列,返回并启动一个Task,当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
                });
                Console.WriteLine("主线程创建");
    
            }

    执行结果如下:

     可以看到线程的创建 并不影响主线程的创建,说明Task任务不会阻塞主线程,我们也可以创建带有返回值的Task<TResult>,创建方法和没有返回值的基本一样

    代码如下:

            static void Main(string[] args)
            {
                CreatTaskMethod();
                Console.ReadKey();
            }
    
            /// <summary>
            /// 创建带有返回值的Task任务
            /// </summary>
            public static void CreatReturnTask()
            {
                //方式一:
                Task<string> task = new Task<string>(() => {
                   return ($"Task第一种创建方式:通过实例化一个Task,然后通过Start方法启动,当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
                });
                task.Start();
                //方式二:
                Task<string> task2 = Task<string>.Factory.StartNew(() => {
                    return ($"Task第二种创建方式:直接创建并启动Task,当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
                });
                //方式三:
                Task<string> task3 = Task<string>.Run(() => {
                  return ($"Task第三种创建方式:将任务放在线程池队列,返回并启动一个Task,当前线程Id为{Thread.CurrentThread.ManagedThreadId}");
                });
                Console.WriteLine("主线程创建");
                Console.WriteLine(task.Result);
                Console.WriteLine(task2.Result);
                Console.WriteLine(task3.Result);
            }

    执行结果:

    注意:  task.Result 获取结果时会阻塞线程,即如果task没有执行完成,会等待task执行完成以后获取结果Result,然后在执行后面的代码

    可以把 Console.WriteLine("主线程创建"); 放到现在获取Result结果后面会的到

    这里会发现主线程被阻塞了。

    Task也可以同步执行,不会阻塞主线程。Task提供了 RunSynchronously()用于同步执行Task任务,代码如下:

    public static void SyncTask()
            {
                for (int i = 0; i < 10; i++)
                {
                    Task task = new Task(() => {
                        Thread.Sleep(100);
                        Console.WriteLine($"执行结果Task结束,当前执行顺序为{i}");
                    });
                    task.RunSynchronously();
    
                }
                Console.WriteLine("主线程执行结束");
            }

    执行结果如下:

     这里可以看到 Task执行线程是有顺序的,在使用RunSynchronously以后,会阻塞主线程,达到同步执行。除了使用RunSynchronously方法阻塞线程外,我们还可以使用(wait/waitAll/waitAny)阻塞Task

    这里先说明一下Thread线程的阻塞方法

    方法一:使用thread.join() 方法可以阻塞主线程 例:

    static void Main(string[] args)
            {
                Thread th1 = new Thread(() => {
                    Thread.Sleep(500);
                    Console.WriteLine("线程1执行完毕!");
                });
                th1.Start();
                Thread th2 = new Thread(() => {
                    Thread.Sleep(1000);
                    Console.WriteLine("线程2执行完毕!");
                });
                th2.Start();
                //阻塞主线程
                th1.Join();
                th2.Join();
                Console.WriteLine("主线程执行完毕!");
                Console.ReadKey();
            }

    Task的Wait/WaitAny/WaitAll方法

    Thread的join 方法可以阻塞调用线程,但弊端是不利于调用,如果需要阻塞的线程比较多,需要每个线程都要调用一次 Thread.join() 方法;

    另外如果我们想要某一线程或者全部线程执行完毕以后,立即解除线程阻塞,使用 join不容易实现,而Task提供了这种机制,下面我们逐一解释一下

    Wait()  表示等待Task执行完毕,功能类似于thread,join();  

    WaitAll(Task[] tasks) 表示只有所有的task都执行完毕以后在接触阻塞;

    WaitAny(Task[] tasks) 表示只要有一个task执行完毕就解除阻塞。列:

            public static void TaskWaitAll()
            {
                Task task = Task.Factory.StartNew(() => {
                    Console.WriteLine("线程1执行完毕");
                });
                Task task1 = Task.Run(() => {
                    Console.WriteLine("线程2执行完毕");
                });
                Task.WaitAll(new Task[] { task,task1 });
                Console.WriteLine("主线程执行完毕!");
            }

    执行结果如下:

     可以看到线程是异步执行在我们指定了WaitAll线程之后 主线程是出于阻塞状态的,当WaitAll下所有线程执行完毕以后,主线程才解除阻塞,执行,如果使用了

    WaitAny 执行得到的结果就是 当线程一或者线程二任意一个执行完毕的时候 主线程就会解除阻塞,继续进行

    注意:Wait 、WaitAll、WaitAny方法返回值都是void 这些方法单纯的实现阻塞线程。

    如果想要让所有Task执行完毕,或则让任意Task执行完毕后,开始解除阻塞执行后续操作,该如何实现呢?这时候需要用到WhenAny、WhenAll方法了这些方法执行完毕以后会返回一个task实力。

    Task中的延续操作(WhenAny/WhenAll/ContinueWith)

    task.WhenAll(Task[] tasks) 表示所有的task都执行完毕以后再去执行后续的操作。

    task.WhenAny(Task[] tasks)表示任意一个task执行完毕以后就开始后续操作。列:

            static void Main(string[] args)
            {
                TaskWhenAll();
                Console.ReadKey();
            }
            /// <summary>
            /// WhenAll  WhenAny
            /// </summary>
            public static void TaskWhenAll()
            {
                Task task1 = Task.Run(() => {
                    Console.WriteLine("线程1执行完毕");
                });
                Task task2 = Task.Factory.StartNew(() => {
                    Console.WriteLine("线程2执行完毕");
                });
                //Task.WhenAll(task1, task2).ContinueWith((t) => {
                //    Console.WriteLine("执行后续操作");
                //});
                Task.WhenAny(task1, task2).ContinueWith((t) => {
                    Console.WriteLine("任意线程执行完毕后执行");
                });
                Console.WriteLine("主线程执行完毕");
            }

     

    执行结果如下:

    可以看到使用WhenAll、WhenAny时方法不会阻塞线程

    上边的例子也可以通过Task.Factory.ContinueWhenAll(Task[] tasks,Action continuationAction) 和 Task.Factory.ContinueWhenAny(Task[] task, Action continuationAction)实现 例:

            public static void TaskWhenAll()
            {
                Task task1 = Task.Run(() => {
                    Console.WriteLine("线程1执行完毕");
                });
                Task task2 = Task.Factory.StartNew(() => {
                    Console.WriteLine("线程2执行完毕");
                });
                //Task.WhenAll(task1, task2).ContinueWith((t) => {
                //    Console.WriteLine("执行后续操作");
                //});
                //Task.WhenAny(task1, task2).ContinueWith((t) => {
                //    Console.WriteLine("任意线程执行完毕后执行");
                //});
                Task task3 = Task.Factory.ContinueWhenAll(new Task[] { task1,task2}, (t) => {
                    Console.WriteLine("线程全部执行完毕以后,执行");
                });
                Task task4 = Task.Factory.ContinueWhenAny(new Task[] { task1,task2 },(t)=> {
                    Console.WriteLine("任意线程执行完毕以后,执行");
                });
                Console.WriteLine("主线程执行完毕");
            }

    这两种写法不同 但是功效是相同的

     Task的任务取消

    先看一下 Thread是如何取消任务执行的

    1 设置变量来控制任务是否停止,如var isStop=false; 如果值为true 就停止代码如下

    static void Main(string[] args)
            {
                bool isStop = false;
                int index = 0;
                //开启一个线程执行任务
                Thread th1 = new Thread(() =>
                  {
                      while (!isStop)
                      {
                          Thread.Sleep(1000);
                          Console.WriteLine($"第{++index}次执行,线程运行中...");
                      }
                  });
                th1.Start();
                //五秒后取消任务执行
                Thread.Sleep(5000);
                isStop = true;
                Console.ReadKey();
            }

    Task是如何取消任务执行的呢?Task中有一个专门的类CancellationTokenSource 来取消任务执行,还是上面的例子,修改后如下:

            /// <summary>
            /// 取消任务
            /// </summary>
            public static void CancelTask()
            {
                CancellationTokenSource source = new CancellationTokenSource();
                int index = 0;
                Task task = Task.Factory.StartNew(() => {
                    while (!source.IsCancellationRequested)
                    {
                        Console.WriteLine($"第{++index}次执行,线程运行中......");
                    }
                });
                //0.1秒后取消任务执行
                //取消方法1:
                Thread.Sleep(100);
                source.Cancel();//这个时候IsChancellationRequested会变成true 还可以用 
                //取消方法2:
                //source.CancelAfter(100); //这个是0.1秒后自动取消的,和上面方法是等价的
    
            }

    了解了线程创建,执行,销毁,那么跨线程操作也是很有必要了解一下的

    跨线程执行

     public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                Task.Run(() => {
                    Action<int> setValue = (p) => { textBox1.Text = p.ToString(); };
                    for (int i = 0; i < 1000000; i++)
                    {
                        textBox1.Invoke(setValue,i);
                    }
                });
            }
        }

    异步方法(async/await)

    在C#5.0中出现的,让异步编程变得更简单。我们看一个例子:

            static void Main(string[] args)
            {
                MethodOne();
                MethodTwo();
                Console.ReadKey();
            }
    
            public static async Task MethodOne()
            {
                await Task.Run(() => {
                    for (int i = 0; i < 50; i++)
                    {
    Thread.Sleep(100); Console.WriteLine(
    "111"); } }); } public static void MethodTwo() { for (int i = 0; i <30; i++) {
    Thread.Sleep(100); Console.WriteLine(
    "222"); } }

    执行结果

     可以看到虽然先调用了方法一 但是方法二 由于是异步 也没影响方法1的执行

     

  • 相关阅读:
    hh
    SDUT 3923 打字
    最短路
    阶乘后面0的个数(51Nod 1003)
    大数加法
    Biorhythms(中国剩余定理)
    usaco-5.1-theme-passed
    usaco-5.1-starry-passed
    usaco-5.1-fc-passed
    usaco-4.4-frameup-passed
  • 原文地址:https://www.cnblogs.com/Li-yuan/p/13541724.html
Copyright © 2020-2023  润新知