C# .Net学习笔记—— 异步和多线程(Task)
一、概念
Task是DotNet3.0之后所推出的一种新的使用多线程的方式,它是基于ThreadPool线程进行封装的。
二、使用多线程的时机
任务能够并发运行的时候,提升速度;优化体验
三、基本使用方法
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")}"); }
方法一:
Task.Run(() => this.DoSomethingLong("BtnTask_Click1"));
方法二(这种方式是在DotNet4.0之后出现的):
TaskFactory taskFactory = Task.Factory; taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3"));
方法三:
new Task(() => this.DoSomethingLong("btnTask_Click4")).Start();
四、小案例
题目:现在我有一个项目,产品经理负责提需求,设计产品(主线程)。五位同学分别执行策划、前端、后端、数据库、服务器(子线程)。等所有工作完成后(所有子线程运行结束),再通知甲方过来验收
//什么时候用多线程?
//任务能并发进行;
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是非阻塞方法,不会导致卡界面
?五、控制最大线程的并发数量
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万个任务):
{ //用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=>{}里面
Task.Run(()=>this.Coding("哈哈哈","参数2")).ContinueWith(t=>{});
?八、延迟和等待
Task.Delay(1000);//延迟 不会卡
Thread.Sleep(1000);// 等待 卡
??九、Parallel
1、基本介绍:Parallel是基于Task封装的,并行编程。在4.5以后版本出现
2、三种启动方式:
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个任务
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 3;
Parallel.For(0, 5, parallelOptions, i => this.Coding("哈哈哈", "Client" + i));
这样得出的结果,任意时刻都只有3个线程在工作
4、不卡的方式,用Task再包一层
Task.Run(() =>
{
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 3;
Parallel.For(0, 5, parallelOptions, i => this.Coding("哈哈哈", "Client" + i));
});
?5、线程结束?
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!