什么时候能用多线程? 任务可以并发操作的时候。
多线程能干嘛? 提升程序执行速度,提供用户体验。
声明方法DoSomeThingLong方法,用于模拟业务的执行过程。
public long DoSomeThingLong(string name)
{
Console.WriteLine($" 子线程 {name} 启动 {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
long lResult = 0;
for (int i = 0; i < 990000000; i++)
{
lResult += i;
}
Console.WriteLine($" 子线程 End {name} 结束 {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
return lResult;
}
线程三种启动方式:(无返回值)
private void BtnTask_Click(object sender, EventArgs e)
{
//不带返回值
{
Console.WriteLine($" 主线程启动 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
//方式一
{
Task task = new Task(() => this.DoSomeThingLong("BtnTask_Click_1"));
task.Start();
}
//方式二
{
Task<long> task = Task.Run(() => this.DoSomeThingLong("BtnTask_Click_2"));
}
//方式三
{
TaskFactory taskFactory = Task.Factory;
Task<long> task = taskFactory.StartNew(() => this.DoSomeThingLong("BtnTask_Click_3"));
}
Console.WriteLine($" 主线程结束 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
}
执行结果:
从执行结果可以看出,主线程是不受子线程的阻塞,子线程还没开始执行,主线程已经执行结束了。
线程三种启动方式:(有返回值)
//带返回值
{
Console.WriteLine($" 主线程启动 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
//方法一:无法接收返回值
{
Task task = new Task(() => this.DoSomeThingLong("BtnTask_Click_1"));
task.Start();
}
//方法二:可以接收返回值
{
Task<long> task = Task.Run(() => this.DoSomeThingLong("BtnTask_Click_2"));
Console.WriteLine($"2的返回值为:{task.Result}");
}
//方法三:可以接收返回值
{
TaskFactory taskFactory = Task.Factory;
Task<long> task = taskFactory.StartNew(() => this.DoSomeThingLong("BtnTask_Click_3"));
Console.WriteLine($"3的返回值为:{task.Result}");
}
Console.WriteLine($" 主线程结束 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
执行结果:
使用Task.Result方法来接收Task的返回值。从执行结果可以看出,主线程受子线程的阻塞,子线程全部执行结束时,主线程才执行结束。
Thread.Sleep和Task.Delay的使用:
Thread.Sleep是同步等待,指当前线程等待2s后继续执行。
Task.Delay异步等待,指等待2s后启动新任务。
代码示例:
//Thread.Sleep是同步等待,指当前线程等待2s后继续执行
{
Console.WriteLine($" 主线程启动 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine("在Sleep之前");
Thread.Sleep(2000);
Console.WriteLine("在Sleep之后");
stopwatch.Stop();
Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}");
Console.WriteLine($" 主线程结束 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
//Task.Delay异步等待,指等待2s后启动新任务
{
Console.WriteLine($" 主线程启动 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine("在Delay之前");
Task task = Task.Delay(2000).ContinueWith(t =>
{
stopwatch.Stop();
Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
Console.WriteLine("hello");
});
Console.WriteLine("在Delay之后");
Console.WriteLine($" 主线程结束 线程ID为: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
执行结果:
Thread.Sleep(2000)会阻塞当前线程,等待2s后继续执行下面代码。
Task.Delay(2000)不会阻塞当前线程,而是开启一个任务,此任务会在2s后开始启动。上面的实例中就是在2s后开启一个新的线程,新线程打印了程序耗时和‘hello’。
下面结合一个应用场景使用多线程:
场景:老师讲课(只有一个老师),课程结束后布置项目实战作业,同学们协作完成作业(多个学生同时做),如果有任何一个学生的任务完成,老师开始部署环境(准备项目发布),当所有学生的任务都完成,老师集中点评。
先写两个方法,用于模拟老师教学和学生完成作业的过程:
/// <summary>
/// 老师教学
/// </summary>
/// <param name="lesson"></param>
private void Teach(string lesson)
{
Console.WriteLine($"{lesson}开始讲...");
long lResult = 0;
for (int i = 0; i < 99999999; i++)
{
lResult += i;
}
Console.WriteLine($"{lesson}讲完了...");
}
/// <summary>
/// 学生完成作业
/// </summary>
/// <param name="name"></param>
/// <param name="projectName"></param>
private void Coding(string name, string projectName) {
Console.WriteLine($"Coding 开始 {name} {projectName} 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
long lResult = 0;
for (int i = 0; i < 999999; i++)
{
lResult += i;
}
Console.WriteLine($"Coding 结束 {name} {projectName} 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
老师开始,学生完成作业:
Console.WriteLine("老师开启了一学期的课程");
this.Teach("Lesson1");
this.Teach("Lesson2");
this.Teach("Lesson3");
Console.WriteLine("课程教学结束,布置实战作业,多人合作完成");
TaskFactory taskFactort = new TaskFactory();
List<Task> taskList = new List<Task>();
taskList.Add(taskFactort.StartNew(()=>this.Coding("张三","后台管理")));
taskList.Add(taskFactort.StartNew(() => this.Coding("李四", "数据库")));
taskList.Add(taskFactort.StartNew(() => this.Coding("王五", "客户端")));
taskList.Add(taskFactort.StartNew(() => this.Coding("赵六", "后台服务")));
//阻塞当前线程,等着任意一个任务完成
Task.WaitAny(taskList.ToArray());
Console.WriteLine("老师准备环境开始部署项目");
//等待全部线程完成后再继续 阻塞当前线程
Task.WaitAll(taskList.ToArray());
Console.WriteLine("实战作业全部完成后,老师集中点评");
执行结果:
从执行结果可以看出,王五首先完成了作业,老师就可以准备环境。当所有学生都完成作业后,老师进行点评。
还有另外一种方式也可以实现当任意一个子线程执行结束后执行某个操作,当所有子线程执行结束后执行某个操作。
Console.WriteLine("老师开启了一学期的课程");
this.Teach("Lesson1");
this.Teach("Lesson2");
this.Teach("Lesson3");
Console.WriteLine("课程教学结束,布置实战作业,多人合作完成");
TaskFactory taskFactort = new TaskFactory();
List<Task> taskList = new List<Task>();
taskList.Add(taskFactort.StartNew(()=>this.Coding("张三","后台管理")));
taskList.Add(taskFactort.StartNew(() => this.Coding("李四", "数据库")));
taskList.Add(taskFactort.StartNew(() => this.Coding("王五", "客户端")));
taskList.Add(taskFactort.StartNew(() => this.Coding("赵六", "后台服务")));
//阻塞当前线程,等着任意一个任务完成
//Task.WaitAny(taskList.ToArray());
//Console.WriteLine("老师准备环境开始部署项目");
//等待全部线程完成后再继续 阻塞当前线程
//Task.WaitAll(taskList.ToArray());
//Console.WriteLine("实战作业全部完成后,老师集中点评");
taskFactort.ContinueWhenAny(taskList.ToArray(), t =>
{
Console.WriteLine($"XXX开发完成,获取一个红包奖励 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
//所有学生实战作业完成后,一起庆祝
taskFactort.ContinueWhenAll(taskList.ToArray(), t =>
{
Console.WriteLine($"开发全部完成,一起庆祝 线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
执行结果:
ContinueWhenAny、ContinueWhenAll 属于非阻塞式调用;使用的线程可能是新线程,也可能是刚完成任务的子线程,不可能是主线程。