上一篇文章(C# Thread类 - 多线程学习1)说过多线程之Thread 类,下面继续学习多线程的另一个更重要的知识点:Task 类,如果有时间也建议看下上面的这篇文章,或许有用呐。
Thread线程是用来创建并发的一种低级别工具,它具有一些限制,尤其是:
- 虽然开始线程的时候可以方便的传入数据,但是当join的时候很难从线程获得返回值。
- 可能需要设置一些共享字段。
- 如果操作抛出异常,铺货和传播该异常都很麻烦
- 无法告诉线程在结束时开始另外的工作,你必须进行join操作(在进程中阻塞当前的线程)
- 很难使用较小的并发(concurrent)来组件大型的并发
Task类可以很好的解决上述问题,它是一个高级抽象:它代表了一个并发操作(concurrent),该操作可能有Thread支持,或不由Thread支持。
- Task是可组合的(可使用continuation把他们穿成链)。
- Tasks可以使用线程池来减少启动延迟。
- 使用TaskCompletionSource,Tasks可以利用回调的方式,在等待I/O绑定操作时完全避免使用线程。
Task建立多线程有多种方法,下面通过代码进行说明。代码可能有点长,但是把它分块就比较好理解的,因为是多种方法合并在一起进行介绍的,所以整个代码与输出结果比较长。
直接上代码,后面有对代码的解析:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ch_Task
{
class Program
{
static void CallTask2(string name)
{
Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:" + name);
for (int i = 1; i <= 5; i++)
{
Thread.Sleep(500);
Console.WriteLine($"{i.ToString()} ---name:task2");
}
Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:" + name);
}
public static void CallTask3()
{
Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:task3");
for (int i = 1; i <= 5; i++)
{
Thread.Sleep(500);
Console.WriteLine($"{i.ToString()} ---name:task3");
}
Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:task3");
}
public static void CallTask3Continue(Task t)
{
Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:task3.continue");
for (int i = 1; i <= 5; i++)
{
Thread.Sleep(500);
Console.WriteLine($"{i.ToString()} ---name:task3.continue");
}
Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:task3.continue");
}
async static void task4()
{
Console.WriteLine($"taskID:{Task.CurrentId} beginning! ---name:task4");
for (int i = 1; i<= 5; i++)
{
Console.WriteLine($"{i.ToString()} ---name:task4");
await Task.Delay(500);
}
Console.WriteLine($"taskID:{Task.CurrentId} finished! ---name:task4");
}
static void Main(string[] args)
{
//-----------方式1-----------------
Task task1 = new Task(() =>
{
Console.WriteLine($"taskID:{Task.CurrentId} beginning! ------name:task1");
for(int i=1;i<=5;i++)
{
Thread.Sleep(500);
Console.WriteLine($"{i.ToString()} ---name:task1");
}
Console.WriteLine($"taskID:{Task.CurrentId} finished! ------name:task1");
});
task1.Start();
// program execution after task1 finish
task1.ContinueWith((task) =>
{
Console.WriteLine($"taskID:{task.Id}.continue begin and this task id is {Task.CurrentId}");
for (int i = 1; i <= 5; i++)
{
Thread.Sleep(500);
Console.WriteLine($"{i.ToString()} ---name:task1.continue");
}
Console.WriteLine($"taskID:{Task.CurrentId} finished! ------name:task{task.Id}.continue");
});
//-------------方式2----------------
var task2 = new Task(() => CallTask2("Task2"));
task2.Start();
//------------方式3------------
Task task3 = new Task(CallTask3);
task3.Start();
Task task3Continue = task3.ContinueWith(CallTask3Continue);
task3Continue.Wait(); // 阻塞主线程,直到task3Continue 线程执行结束
//-------------方式4-------------
task4();
Console.ReadKey();
}
}
}
输出结果为:
taskID:1 beginning! ------name:task1
taskID:3 beginning! ---name:Task2
taskID:4 beginning! ---name:task3
1 ---name:task1
1 ---name:task2
1 ---name:task3
2 ---name:task1
2 ---name:task2
2 ---name:task3
3 ---name:task1
3 ---name:task2
3 ---name:task3
4 ---name:task1
4 ---name:task3
4 ---name:task2
5 ---name:task1
taskID:1 finished! ------name:task1
taskID:1.continue begin and this task id is 2
5 ---name:task3
5 ---name:task2
taskID:4 finished! ---name:task3
taskID:3 finished! ---name:Task2
taskID:5 beginning! ---name:task3.continue
1 ---name:task1.continue
1 ---name:task3.continue
2 ---name:task1.continue
2 ---name:task3.continue
3 ---name:task3.continue
3 ---name:task1.continue
4 ---name:task3.continue
4 ---name:task1.continue
5 ---name:task3.continue
5 ---name:task1.continue
taskID:5 finished! ---name:task3.continue
taskID:2 finished! ------name:task1.continue
taskID: beginning! ---name:task4
1 ---name:task4
2 ---name:task4
3 ---name:task4
4 ---name:task4
5 ---name:task4
taskID: finished! ---name:task4
下面对输出结果进行分析:
直接看主函数部分,可以看到共有task1、task1.continue、task2、task3、task3.continue、task4六个子线程、分别用了四种方法:
task1 使用 : Task task1 = new Task(() =>{ } );直接去创建子线程。其实,这个运行的话可以简化即:
将下面代码:
Task task1 = new Task(() =>{ } );
task1.start()
换成 : Task.Run(() =>{ } ); 即可
task2 使用: var task2 = new Task(() => CallTask2("Task2")) 创建子线程。
task3 使用: Task task3 = new Task(CallTask3);直接创建子线程
task4 使用异步方法去创建子线程,关键词:async, 可以看到在task4之前,使用了task3Continue.Wait();
这个语句可以用来阻塞主线程,直到task3Continue 线程执行结束后才会 task4线程的定义与运行。
另外,可以看到,在别的task使用延时用的都是 Thread.Sleep(500); 而使用异步方法创建的子线程在使用延时
的时候使用的是 await Task.Delay(500);这一点需要留意。
task1.continue 线程 即 task1.ContinueWith((task) => {})在task1 完全执行结束后才执行,
同理
task3.continue在task3 完全结束之后才执行。
Task.ContinueWith((task) => {})可以将子线程串联起来