C#多线程之Task🔥
在C#中,ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便,
1.ThreadPool不支持线程的取消、完成、失败通知等交互性操作;
2.ThreadPool不支持线程执行的先后次序。
Task在线程池的基础上进行了优化,任务Task和线程Thread的区别:
1、任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行。
2、任务跟线程不是一对一的关系,比如开100个任务并不是说会开100个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。
Task 是用于表示异步操作的一种机制。Task 类型是.NET Framework中的一部分,用于处理并发操作和异步编程。它允许你在后台执行耗时的操作,而不会阻塞主线程。下面是关于Task的一些重要信息:
# 创建任务Task
使用线程池的QueueUserWorkItem方法可以向线程池提交一个待执行的任务。该方法接受一个WaitCallback委托作为参数,表示要执行的任务。示例代码如下:
Task.Run(() => DoSomething());
//或者
Task.Factory.StartNew(() => DoSomething());
//或者
Task task = new Task(() => DoSomething());
task.Start();
2
3
4
5
6
# 有返回值的Task
如果要向线程池提交带参数的任务,可以使用匿名方法或lambda表达式来实现。示例代码如下:
Task<int> t1 = Task.Run<int>(() => {
return 1;
});
//通过t1.Result查看返回的结果
Console.WriteLine(t1.Result);
2
3
4
5
# 取消任务
//1s后自动取消线程
CancellationTokenSource cts = new CancellationTokenSource(1000);
//为取消线程注册回调函数
cts.Token.Register(() => {
Console.WriteLine("线程已取消");
});
Task.Run(() => {
Console.WriteLine("开始执行");
Thread.Sleep(2000);
//判断当前线程是否已被取消
if (cts.Token.IsCancellationRequested)
{
Console.WriteLine("方法已结束");
return;
}
Console.WriteLine("线程继续执行");
}, cts.Token);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 延续任务
Task task1 = Task.Run(() =>
{
Thread.Sleep(100);
Console.WriteLine("任务1完成!");
});
Task task2 = task1.ContinueWith((t) =>
{
Thread.Sleep(200);
Console.WriteLine("任务2完成!");
});
Task task3 = Task.Run(() =>
{
Thread.Sleep(300);
Console.WriteLine("任务3完成!");
});
Task.WaitAll(task2, task3);
Console.WriteLine("所有任务已完成!");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 等待任务完成
Task.Wait();
# 等待所有任务完成
如果需要等待所有通过线程池提交的任务完成,可以使用ManualResetEvent等同步机制来实现。示例代码如下:
//存放所有线程
List<Task> lst = new List<Task>();
//开启10个线程
for (int i = 0; i < 10; i++)
{
lst.Add(Task.Run(() => {
Thread.Sleep(new Random().Next(1, 3000));
Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}");
}));
}
//等待所有线程执行完毕
Task.WaitAll(lst.ToArray());
Console.WriteLine("所有线程执行完毕");
2
3
4
5
6
7
8
9
10
11
12
13
# 等待任意线程执行完毕
Task.WaitAny(lst.ToArray());
# 异步等待任务执行
class Program
{
static async Task Main()
{
Task task1 = Task.Run(() => DoWork("Task 1"));
Task task2 = Task.Run(() => DoWork("Task 2"));
Task task3 = Task.Run(() => DoWork("Task 3"));
// 等待所有任务完成
await Task.WhenAll(task1, task2, task3);
Console.WriteLine("All tasks have completed.");
// 在所有任务完成后执行其他操作
Console.WriteLine("Main thread continues to execute...");
Console.ReadLine();
}
static void DoWork(string taskName)
{
Console.WriteLine($"Task {taskName} is running on a background thread.");
Task.Delay(2000).Wait(); // Simulating some work
Console.WriteLine($"Task {taskName} completed its work.");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Task.WhenAll 和 Task.WaitAll 都用于等待多个任务完成,但它们之间有几个重要的区别:
1.异步性:
Task.WhenAll 是异步方法,它返回一个新的任务,该任务在所有指定的任务都完成时完成。
Task.WaitAll 是同步方法,它会阻塞当前线程,直到所有指定的任务都完成或超时。
2.使用方式:
Task.WhenAll 通常与 await 关键字一起使用,以非阻塞的方式等待多个任务完成。
Task.WaitAll 通常在同步代码中使用,需要等待所有任务完成后再继续执行后续代码。
3.返回值:
Task.WhenAll 返回一个新的任务,该任务在所有指定的任务都完成时完成。你可以使用 await 关键字来等待这个任务完成。
Task.WaitAll 没有返回值,它只是在所有任务完成后返回。
4.异常处理:
Task.WhenAll 在所有任务完成后返回的任务将包含所有任务的异常信息。如果其中任何一个任务抛出异常,你可以通过检查返回的任务的异常来处理它们。
Task.WaitAll 如果任何一个任务抛出异常,WaitAll 方法会将其中一个异常重新抛出,而不提供任何特定的信息来区分哪个任务抛出了异常。
综上所述,Task.WhenAll 更适合异步操作和非阻塞代码,而 Task.WaitAll 更适合同步代码和需要阻塞当前线程的情况。