C# .Net学习笔记—— 异步和多线程(Task)

一、概念

Task是DotNet3.0之后所推出的一种新的使用多线程的方式,它是基于ThreadPool线程进行封装的。

二、使用多线程的时机

任务能够并发运行的时候,提升速度;优化体验

三、基本使用方法

cs 复制代码
        private void button5_Click(object sender, EventArgs e)
        {
            Log.Info($"Task Start {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            Task.Run(() => this.DoSomethingLong("BtnTask_Click1"));
            Task.Run(() => this.DoSomethingLong("BtnTask_Click2"));
            Log.Info($"Task End {Thread.CurrentThread.ManagedThreadId.ToString("00")}  {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        }

        private void DoSomethingLong(string name)
        {
            Log.Info($"DoSomethingLong Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {name} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            long result = 0;
            for (int i = 0; i < 10000000; i++)
            {
                result += i;
            }
            Thread.Sleep(2000);
            Log.Info($"DoSomethingLong End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {name} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        }

方法一:

cs 复制代码
Task.Run(() => this.DoSomethingLong("BtnTask_Click1"));

方法二(这种方式是在DotNet4.0之后出现的):

cs 复制代码
TaskFactory taskFactory = Task.Factory;
taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3"));

方法三:

cs 复制代码
new Task(() => this.DoSomethingLong("btnTask_Click4")).Start();

四、小案例

**题目:**现在我有一个项目,产品经理负责提需求,设计产品(主线程)。五位同学分别执行策划、前端、后端、数据库、服务器(子线程)。等所有工作完成后(所有子线程运行结束),再通知甲方过来验收

cs 复制代码
       //什么时候用多线程?
        //任务能并发进行;
        private void button7_Click(object sender, EventArgs e)
        {
            List<Task> taskList = new List<Task>();

            Log.Info($"项目经理启动一个项目...{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Log.Info($"前置的准备工作...{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Log.Info($"开始变成...{Thread.CurrentThread.ManagedThreadId.ToString("00")}");

            taskList.Add(Task.Run(() => this.Coding("周同学", "负责策划")));
            taskList.Add(Task.Run(() => this.Coding("黄同学", "负责前端")));
            taskList.Add(Task.Run(() => this.Coding("林同学", "负责后端")));
            taskList.Add(Task.Run(() => this.Coding("陈同学", "负责数据库")));
            taskList.Add(Task.Run(() => this.Coding("陈同学", "负责服务器")));

            //会阻塞当前线程,等着某个任务完成后才进行下一行,卡界面
            Task.WaitAny(taskList.ToArray());
            Log.Info($"已经有一个人完成了...{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            //多线程加快速度,但是全部任务完成后才能执行的操作
            Task.WaitAll(taskList.ToArray()); //会阻塞当前线程,等着全部任务完成后,才会进入下一行,卡界面
            Log.Info("告诉甲方验收,上线使用");


            //Task.WhenAny(taskList.ToArray()).ContinueWith(t =>
            //{
            //    Log.Info($"哈哈哈哈我第一个做完了...{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            //});
            //Task.WhenAll(taskList.ToArray()).ContinueWith(t =>
            //{
            //    Log.Info($"部署环境,联调测试...{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            //});
        }


        private void Coding(string name, string task)
        {
            Log.Info($"Coding {name} Start {task} 线程号:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            long result = 0;
            for (int i = 0; i < 1000000000; i++)
            {
                result += i;
            }
            Log.Info($"Coding {name} End {task} 线程号:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        }

知识点:

1、这里**Task.WaitAll()**会阻塞当前线程,等着全部任务完成后才会继续下一行。这种方式会卡界面

2、**Task.WaitAll()**第二个参数也可以带参,限时等待

3、**Task.WaitAny(taskList.ToArray()) ;**会阻塞当前线程,等着某个任务完成后才进行下一行,卡界面
WaitAll的使用时机(以某宝为例):

一个业务查询操作有多个数据源,首页-多线程并发-拿到全部数据后才能返回

WaitAny的使用时机(以某宝为例):

一个商品搜索有多个数据源,商品搜索-多个数据源-多线程并发-只需要一个结果即可
WaitAll和WaitAny都是阻塞方法,需要完成后再继续,这会导致卡界面
WhenAll和WhenAny是非阻塞方法,不会导致卡界面

五、控制最大线程的并发数量

cs 复制代码
       private void button8_Click(object sender, EventArgs e)
        {
            Log.Info($"Coding Start 线程号:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

            ThreadPool.SetMinThreads(8, 8);
            for (int i = 0; i < 100; i++)
            {
                Task.Run(() => 
                {
                    Log.Info($"{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Thread.Sleep(2000);
                });
            }
        }

这里通过SetMaxThreads去控制最大的线程并发数量,这种方法并不好,因为ThreadPool是全局的。下面写一种比较合适的方法(用10个线程完成1万个任务):

cs 复制代码
            {
                //用10个线程完成1万个任务
                List<int> list = new List<int>();
                for (int i = 0; i < 10000; i++)
                {
                    list.Add(i);
                }
                Action<int> action = i =>
                {
                    Log.Info($"{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Thread.Sleep(new Random().Next(100, 300));
                };
                List<Task> taskList = new List<Task>();
                //这里先启动任务
                foreach (var i in list)
                {
                    int k = i;
                    taskList.Add(Task.Run(() => action.Invoke(k)));
                    //判断个数
                    if (taskList.Count > 10)
                    {
                        //这里开始等着
                        Task.WaitAny(taskList.ToArray());
                        //等到某个线程完成后,把还没完成的留着,已完成的清掉。
                        //这样就可以完成对数量的控制
                        taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
                    }
                }
                Task.WhenAll(taskList.ToArray());
            }

六、Task任务添加标识

方法一:TaskFactory的StartNew方法可以有一个重载,传入state作为标识

可以看到在Task任务结束后,通过AsyncState可以打印出我们的标识。这种方式我们就可以知道是谁先完成的

方法二:Task方法并没有直接使用的API来传递一个标识,

七、Task回调

t指的是参数2,回调写到t=>{}里面

cs 复制代码
Task.Run(()=>this.Coding("哈哈哈","参数2")).ContinueWith(t=>{});

八、延迟和等待

cs 复制代码
Task.Delay(1000);//延迟  不会卡
Thread.Sleep(1000);// 等待 卡

九、Parallel

1、**基本介绍:**Parallel是基于Task封装的,并行编程。在4.5以后版本出现

2、三种启动方式:

cs 复制代码
 private void button10_Click(object sender, EventArgs e)
        {
            Log.Info($"Coding Start 线程号:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            {
                //启动方式一:
                Parallel.Invoke(() => this.Coding("哇哈哈", "whh"),
                    ()=>this.Coding("啦啦啦", "lll"),
                    ()=>this.Coding("哦哦哦", "ooo"),
                    ()=>this.Coding("呸呸呸", "ppp"));
            }
            {
                //启动方式二:
                //Parallel.For(0, 5, i => this.Coding("哈哈哈", "Client" + i));
            }
            {
                //启动方式三:
                Parallel.ForEach(new string[] { "0", "1", "2" ,"3","4"}, i => this.Coding("哈哈哈", "Client" + i));
            }
            Log.Info($"Coding End 线程号:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        }

3、限制线程数,用3个线程完成10个任务

cs 复制代码
   ParallelOptions parallelOptions = new ParallelOptions();
                parallelOptions.MaxDegreeOfParallelism = 3;
                Parallel.For(0, 5, parallelOptions, i => this.Coding("哈哈哈", "Client" + i));

这样得出的结果,任意时刻都只有3个线程在工作

4、不卡的方式,用Task再包一层

cs 复制代码
           Task.Run(() => 
                {
                    ParallelOptions parallelOptions = new ParallelOptions();
                    parallelOptions.MaxDegreeOfParallelism = 3;
                    Parallel.For(0, 5, parallelOptions, i => this.Coding("哈哈哈", "Client" + i));
                });

5、线程结束

相关推荐
霑潇雨12 分钟前
Spark学习基础转换算子案例(单词计数(WordCount))
java·大数据·分布式·学习·spark·maven
TANGLONG22221 分钟前
【C++】继承详解——基类/派生类、作用域、默认函数、菱形继承(超详细)
java·c语言·c++·经验分享·笔记·ajax
eastyuxiao1 小时前
数字孪生教程(软件篇)官方学习路径
大数据·人工智能·学习·数字孪生
小徐学编程-zZ1 小时前
拆解业务逻辑分析
数据库·学习
知识分享小能手1 小时前
R语言入门学习教程,从入门到精通,R语言获取数据 (7)
开发语言·学习·r语言
木木_王1 小时前
嵌入式学习 | STM32裸板驱动开发(Day01)入门学习笔记(超详细完整版|点灯实验 + 库函数代码 + 原理全解)
linux·驱动开发·笔记·stm32·学习
JAVA面经实录9171 小时前
Java架构师最终完整版学习路线图
java·开发语言·学习
叶子野格1 小时前
数据集网站kaggle注册无验证码选项?
学习
largecode2 小时前
能不能让座机号码显示“XX公司”那样的认证名称?申请号码认证方法
经验分享·笔记·音视频·课程设计·oneapi·segmentfault·微信开放平台
for_ever_love__2 小时前
UI学习:数据驱动ce l l
学习·ui·ios·objective-c