C#——多线程之Task
- 前言
- 一、Task是什么?
- 二、各应用场景以及实例分析
- 1.异步执行代码
- 2.等待异步操作完成
- 3.并行执行多个任务
- 4.处理异常
- 5.取消异步操作
- 三、一些其他问题
- 1.WhenAll与WhenAny的区别
- 总结
前言
在代码编写过程中,经常会用到多线程的知识,实现方法有很多种,突然想总结并理清楚其中的相关性与差异性。故以此开篇总结各种工具的用法与注意事项。本篇将对Task稍作总结。
一、Task是什么?
Task 是 .NET 中用于表示异步操作的类,它提供了一种简单和强大的方式来处理异步编程。Task 可以用于各种应用场景和功能,包括:
- 异步执行代码:Task 允许在单独的线程上执行代码块,从而避免阻塞主线程,提高程序的响应性和并发性。
- 等待异步操作完成:通过 await 关键字可以等待 Task 完成,从而实现非阻塞的异步操作。
- 并行执行多个任务:Task 可以用于并行执行多个独立的任务,从而加快任务的完成速度,提高系统的性能。
- 处理异常:Task 提供了异常处理机制,可以在异步操作中捕获和处理异常。
- 轻量级的线程管理:Task 使用线程池来管理线程,避免了频繁创建和销毁线程的开销,使得线程的管理更加高效。
- 取消异步操作:Task 支持取消操作,可以通过 CancellationToken 来取消正在进行的异步操作。
总的来说,Task 是一种非常有用的工具,它在异步编程中起着至关重要的作用,可以帮助开发人员编写高效、响应式和可靠的异步代码。
二、各应用场景以及实例分析
1.异步执行代码
假设我们需要在后台线程中执行一个耗时的操作,而不阻塞主线程。使用 Task 可以很方便地实现这一点。例如,我们可以使用 Task.Run 方法在后台线程中执行一个计算操作,然后在主线程中继续执行其他代码:
代码如下(示例):
static void Main(string[] args)
{
Task.Run(() =>
{
Console.WriteLine("task");
Task.Delay(2000).Wait(); //模拟耗时操作
Console.WriteLine("taskEnd");
});
Task.Delay(1000).Wait();
Console.WriteLine($"main");
Console.ReadKey();
}
结果如下图:
2.等待异步操作完成
使用 await 关键字可以等待 Task 完成,从而实现非阻塞的异步操作。例如,在 Windows Forms 或 WPF 应用程序中,我们可以在异步方法中使用 await 来等待异步操作完成后更新 UI:
public static async Task wait()
{
await Task.Delay(2000); //将等待2秒后,继续执行下面的代码
Console.WriteLine("Wait 2000 ms");
}
3.并行执行多个任务
假设我们有多个独立的任务,可以使用 Task.WhenAll 方法来等待它们执行完成。
代码如下(示例):
static async Task test()
{
Task[] tasks = new Task[3];
tasks[0] = wait();
tasks[1] = wait2();
tasks[2] = wait3();
await Task.WhenAll(tasks);
Console.WriteLine("main");
Console.ReadKey();
}
public static async Task wait()
{
await Task.Delay(2000);
Console.WriteLine("Wait 2000 ms");
}
public static async Task wait2()
{
await Task.Delay(3000);
Console.WriteLine("Wait 3000 ms");
}
public static async Task wait3()
{
await Task.Delay(4000);
Console.WriteLine("Wait 4000 ms");
}
结果如下图:
ps:
上述代码有个小细节:在 test() 方法中,没有显式地调用 Start() 方法,因为在使用 async/await 时不需要手动启动任务。async 方法本身会确保异步方法在调用时会自动启动,所以不需要手动调用 Start()。
遇到点小问题:
我在通过入下代码,new task进行新建,start进行启动时,出现Main在wait 。。前先被打印出来如下:
static async Task test()
{
Task[] tasks = new Task[3]
{
new Task( ()=> wait()),
new Task( ()=> wait2()),
new Task( ()=> wait3())
};
foreach (var item in tasks)
{
item.Start();
}
await Task.WhenAll(tasks);
Console.WriteLine("main");
Console.ReadKey();
}
Task.WhenAll() 方法只能等待当前线程池中的任务完成。如果任务没有被调度到线程池上运行,那么 Task.WhenAll() 方法将无法等待这些任务完成。
当你使用 Task.Run() 或 Task.Factory.StartNew() 方法创建任务时,这些任务会被自动调度到线程池上运行,因此 Task.WhenAll() 方法可以等待它们的完成。
而通过 new Task() 创建任务对象后,手动调用 Start() 方法来启动任务,但这些任务并没有被自动调度到线程池上运行。因此,Task.WhenAll() 方法无法等待这些任务完成。
4.处理异常
Task 提供了异常处理机制,可以在异步操作中捕获和处理异常。例如,我们可以使用 try-catch 块来处理异步操作中可能抛出的异常:
static void Main(string[] args)
{
test();
Console.WriteLine("main");
Console.ReadKey();
}
static async void test()
{
try
{
await wait();
}
catch (Exception)
{
Console.WriteLine("报错啦!");
}
}
public static async Task wait()
{
await Task.Delay(2000);
Console.WriteLine("Wait 2000 ms");
throw new Exception("wwww");
}
结果如下:
ps:此处需要注意的是try/catch在await上下文时才能生效,否则无法捕获到异常。
5.取消异步操作
Task 支持取消操作,可以通过 CancellationToken 来取消正在进行的异步操作。例如,我们可以在异步方法中检查 CancellationToken 的状态,并在需要时取消操作:
static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task.Run(async () =>
{
try
{
int i = 0;
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000);
Console.WriteLine($"第{i}次等待");
i += 1;
}
cancellationToken.ThrowIfCancellationRequested();//可弹出OperationCanceledException类型的错误
}
catch (OperationCanceledException)
{
Console.WriteLine($"任务被取消");
}
}, cancellationToken);
Thread.Sleep(5000);
cancellationTokenSource.Cancel();//取消任务
Console.ReadKey();
}
结果如下图:
ps:其实通过一个全局布尔变量也可以达到上面中止任务的效果,但cancellationToken中封装了一些取消任务后的回调,减少了自己码代码的时间,但增加了学习理解的成本。
三、一些其他问题
1.WhenAll与WhenAny的区别
WhenAll:是要等待所以的任务完成才返回;如上文并行任务章节内容。
WhenAny:只要一个任务完成,就立马结束等待。如下。
static async Task test()
{
Task[] tasks = new Task[3];
tasks[0] = wait();
tasks[1] = wait2();
tasks[2] = wait3();
await Task.WhenAny(tasks);
Console.WriteLine("main");
Console.ReadKey();
}
总结
上文我们通过各实例内容,粗略了解了Task的各种应用,在使用中还会遇见多种问题,在此无法详尽的列举出来。在以后的过程中,如果遇见问题的话我会陆续更新到这里,希望本文对大家有所帮助。下次我们将继续了解多线程中的Thread。我们下次再见。