C#多线程之Task🔥

1/16/2024 多线程

在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();
1
2
3
4
5
6

# 有返回值的Task

如果要向线程池提交带参数的任务,可以使用匿名方法或lambda表达式来实现。示例代码如下:

 Task<int> t1 = Task.Run<int>(() => {
     return 1;
 });
 //通过t1.Result查看返回的结果
 Console.WriteLine(t1.Result);
1
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);
1
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("所有任务已完成!");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 等待任务完成

Task.Wait();
1

# 等待所有任务完成

如果需要等待所有通过线程池提交的任务完成,可以使用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("所有线程执行完毕");
1
2
3
4
5
6
7
8
9
10
11
12
13

# 等待任意线程执行完毕

Task.WaitAny(lst.ToArray());
1

# 异步等待任务执行

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.");
    }
}
1
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 更适合同步代码和需要阻塞当前线程的情况。