• Thread,ThreadPool,Task, 到async await 的基本使用方法和理解


    很久以前的一个面试场景:

    面试官:说说你对JavaScript闭包的理解吧?

    我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码。

    面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧。

    我:一般没用到多线程。

    面试官:............................. (面试结束)

     好了,哈哈一笑后,我们来看看 Thread,ThreadPool,Task, async, await 的基本使用方法。 

    1.Thread

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
                var thread = new Thread(new ThreadStart(ThreadTest));
                thread.Start();
    
                System.Console.WriteLine("主线程结束");
                System.Console.ReadLine();
            }
    
            private static void ThreadTest()
            {
                System.Console.WriteLine("开始执行子线程.... ");
                Thread.Sleep(100);
            }

    执行结果:

     

    上面的代码,大家应该都很好理解,通过new Thread 来创建一个子线程,然后.Start() 开始执行。

    我们F12 ThreadStart 看到 public delegate void ThreadStart();  是一个无参数无返回值的委托,那么,如果要在线程中执行一个有参数的方法怎么办了?

    OK,我们看Thread的构造函数

    ParameterizedThreadStart 是什么?按字面上意思就是带参数的ThreadStart,继续F12看看它

    果然是可以带一个object的参数。

    改造一下刚才的代码:

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
                var thread = new Thread(new ParameterizedThreadStart(ThreadTest));
                thread.Start(10);
    
                System.Console.WriteLine("主线程结束");
                System.Console.ReadLine();
            }
    
            private static void ThreadTest(object p)
            {
                System.Console.WriteLine("开始执行子线程.... 参数:{0} ", p);
                Thread.Sleep(100);
            } 

    执行结果:

    (当然还可以用ThreadStart(()=>{ }) 直接用lambda表达式的方式,这里就不写示例代码了 )  

     看到上面的执行结果,子线程因为Thread.Sleep(100) ,所以每次都最后才打印出输出结果,那么你可能会疑问,如果我想等子线程执行完,我再执行主线程后面的代码,怎么办?

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
                var thread = new Thread(new ParameterizedThreadStart(ThreadTest));
                thread.Start(10);
                thread.Join();
    
                System.Console.WriteLine("主线程结束");
                System.Console.ReadLine();
            }
    private static void ThreadTest(object p) { System.Console.WriteLine("开始执行子线程.... 参数:{0} ", p); Thread.Sleep(100); }

    注意看, 加了这句 thread.Join() ,管他什么意思,我们先看看执行结果吧!

    OK,是不是明白Join()的意义了? 

    2.ThreadPool

     为什么有了Thread还要出现ThreadPool了?

     如果你的代码设计了大量使用Thread,那么有可能会超过系统最大的线程数导致崩溃,而且每次创建和销毁线程也是很耗资源,ThreadPool就可以帮你提高代码效率并管理你的线程。

     这不是重点,今天重点是学习它的基础使用方法。 

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadTest), 1);
    
                System.Console.WriteLine("主线程结束");
                System.Console.ReadLine();
            }
    
            private static void ThreadTest(object p)
            {
                System.Console.WriteLine("开始执行子线程.... 参数:{0} ", p);
                Thread.Sleep(100);
            }

     先看看WaitCallback的定义

    一个带参数的委托,这就要求它的委托方法必须带一个object的参数了。

    ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池,它不需要我们主动的.Start(),那么他能不能Join()了?

    我们点一下就知道了,它既不要你手动Start也没有Join这样的方法。 

    好了,简单学习Thread和ThreadPool后,发现他们构造函数中都是没有返回值的委托,如果我们要在主线程中获取子线程执行方法的返回值,怎么办? Task闪亮登场了! 

    3.Task

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
                // new Task 创建方式-不带参数
                //Task task = new Task(ThreadTest);
                //task.Start();
    
                // new Task 创建方式-带参数
                //Task task=new Task(() =>  ThreadTest(10));
    
                //Task.Factory 创建方式-不带参数
                //Task task = Task.Factory.StartNew(ThreadTest);
                //task.Start();
    
                //Task.Factory 创建方式-带参数
                //Task task = Task.Factory.StartNew(() => ThreadTest(10));
                //task.Start();
    
                Task task = Task.Run(() => ThreadTest());
                //Task task = Task.Run(() => ThreadTest(10));
    
                System.Console.WriteLine("主线程结束");
                System.Console.ReadLine();
    }
     

    Task 的三种创建线程的方法,Task.Run() 不需要手动Start() 。其他两种方式是需要手动Start()。 他们没有Join()方法,取而代之的是Wait()

    我们用Run()方法为例,看Task如何获取方法的返回值。 

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
    
                Task<int> task = Task.Run(() => ThreadTest(10));
                var result = task.Result;
    
                System.Console.WriteLine("主线程结束,result={0}", result);
                System.Console.ReadLine();
            }
    
            private static int ThreadTest(int i)
            {
                Thread.Sleep(100);
                System.Console.WriteLine("子线程开始");
                return i * 100;
            }

    执行结果:

     通过task.Result 我们获取到了在子线程中ThreadTest方法的返回值。有没有注意,主线程是等子线程执行完之后才打印最后输出的! task.Result 除了拿到返回值外,是不是和Wait()类似?

    看到这里,你肯定会想到,这样另起线程去跑耗时作业和我们平时普通写法有什么区别?效率上会高很多吗?我们来测试看看! 

    常规方法: 

            private static void Main(string[] args)
            {
                DateTime dt1 = DateTime.Now;
                int count = 0;
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(10);
                    count += i;
                }
                System.Console.WriteLine("执行完成,耗时=" + (DateTime.Now - dt1).TotalMilliseconds);
                System.Console.ReadLine();
            }

     

    Task方法:

            private static void Main(string[] args)
            {
                DateTime dt1 = DateTime.Now;
                Task<int> task = Task.Run(() =>
                 {
                     int count = 0;
                     for (int i = 0; i < 10; i++)
                     {
                         Thread.Sleep(10);
                         count += i;
                     }
                     return count;
                 });
    
                var result = task.Result;
    
                System.Console.WriteLine("执行完成,耗时=" + (DateTime.Now - dt1).TotalMilliseconds);
                System.Console.ReadLine();
            }

    这就很尴尬了,用Task反而执行时间更长!!!  是不是我的打开方式不对?  

    4.async  await

    async是修饰一个异步方法的关键字。有两种返回类型(void 或者 Task<T>)

    await必须是在async修饰的异步方法体内,await后面必须是一个异步方法或者Task。表示异步等待后面方法的结果。

    1.返回void的使用方法

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
                for (int i = 0; i < 10; i++)
                {
                    ThreadTest(i);
                }
                System.Console.WriteLine("主线程执行完成");
                System.Console.ReadLine();
            }
    
            private static async void ThreadTest(int i)
            {
                await Task.Run(() =>
                {
                    Thread.Sleep(10);
                    System.Console.WriteLine("子线程开始,i=" + i);
                });
            }

    执行结果

     2.返回Task<T>的使用方法 

            private static void Main(string[] args)
            {
                System.Console.WriteLine("主线程开始");
                var task = ThreadTest();
                System.Console.WriteLine("主线程执行完成,result="+ task.Result);
                System.Console.ReadLine();
            }
            private static async Task<int> ThreadTest()
            {
                var count = 0;
                await Task.Run(() =>
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Thread.Sleep(10);
                        count += i;
                        System.Console.WriteLine("count="+ count);
                    }
                });
                return count;
            }

    返回的是Task<T>,那么像得到它的返回值,肯定也是通过.Result了,我们肯定有疑问了,这样和直接写Task有什么区别? 只是为了更加方便和美观吗?

    接下来我们来测试下执行效率! 

            private static void Main(string[] args)
            {
                DateTime dt1 = DateTime.Now;
                var t = ThreadTest().Result;
                System.Console.WriteLine("执行完成,耗时=" + (DateTime.Now - dt1).TotalMilliseconds + "  count=" + t);
                System.Console.ReadLine();
            }
    
            private static async Task<int> ThreadTest()
            {
                var count = 0;
                await Task.Run(() =>
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Thread.Sleep(10);
                        count += i;
                    }
                });
                return count;
            }

      

     执行效率和之前没什么区别,不知道这种测试方式是否合理?跪求大神们分享赐教!

     今天就写到这里,关于 async  await 和Task区别,async  await 线程阻塞问题,后面再来仔细研究。

    ========================================================================================================

    分割线

    ========================================================================================================

    昨天这篇博客发布后,收到大神的批评和指教,非常感谢!

    读了这篇文章后,才恍然大悟。 文章链接:https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx

    分析下昨天测试“性能”的实例代码的使用错误:

    1.盲目使用task.Result来获取最终结果,这样导致主线程阻塞,都是等待子线程执行完毕。这样的时间差比没有太多意义。

    2.都是在一个Task.Run()中模拟一个耗时操作,内部循环Thread.Sleep(10)。这样把耗时操作搬到一个子线程去做,就算快也能快到哪里去了,完全没有体现出多线程的优越性。

     下面是修改后的测试代码,再看看async await给程序带来的性能:

     1.普通Task,通过task.Result获取返回值。

            private static void Main(string[] args)
            {
                DateTime dt1 = DateTime.Now;
    
                for (int i = 0; i <= 50; i++)
                {
                    var re = Task.Run(() =>
                    {
                        Thread.Sleep(10);
                        return i;
                    }).Result;
    
                    System.Console.WriteLine("result=" + re);
    
                    if (i == 50)
                        System.Console.WriteLine("执行完成,耗时=" + (DateTime.Now - dt1).TotalMilliseconds);
                }
                System.Console.ReadLine();
            }

      2.使用async await

            private static void Main(string[] args)
            {
                DateTime dt1 = DateTime.Now;
    
                for (int i = 0; i <= 50; i++)
                {
                    var task = ThreadTest(dt1, i);
                }
    
                System.Console.ReadLine();
            }
    
            private static async Task<int> ThreadTest(DateTime dt1, int i)
            {
                int re = await Task.Run(() =>
                {
                    Thread.Sleep(10);
                    return i;
                });
    
                System.Console.WriteLine("result=" + re);
    
                if (i == 50)
                    System.Console.WriteLine("执行完成,耗时=" + (DateTime.Now - dt1).TotalMilliseconds);
    
                return re;
            }

     

     async await 真正体现了它的性能所在 。

    总结:

    1.不要盲目使用task.Result

    2.async await的意义(或者说和Task的区别)在于不阻塞线程的情况下获取返回值。

    本文博客园地址:http://www.cnblogs.com/struggle999/p/6933376.html

  • 相关阅读:
    golang中的左值VS右值
    golang指针接收者和值接收者方法调用笔记
    go中如果想要实现别人写的接口,如何保证确实实现了那个接口而不是错过了什么?
    在windows中给git修改默认的编辑器为sublime
    git config 选项
    json包中的Marshal&Unmarshal 文档译本
    go的database/sql库中db.Exce()
    go中导入包的几种方式
    机器学习之分类和聚类的区别
    TP5.0学习笔记
  • 原文地址:https://www.cnblogs.com/struggle999/p/6933376.html
Copyright © 2020-2023  润新知