一、前言
在 C# 中,Task 是异步编程中非常常用的类型。通过 Task,我们可以创建任务、等待任务、获取任务返回值、等待多个任务完成,以及实现任务取消等功能。
本文结合实际代码,介绍几个常用的 Task 方法:
- Task.Run()
- Task.Factory.StartNew()
- new Task() + Start()
- Task.WhenAll()
- Task.WhenAny()
- ContinueWith()
- ContinueWhenAll()
- TaskCreationOptions.LongRunning
- CancellationToken
下面有输出内容跟查看输出内容的操作
二、Task.Run()
Task.Run() 是创建任务的一种常用方式。
基本写法:
Task task = Task.Run(() =>
{
// 需要执行的代码
});
例如:
private async Task Demo_TaskRun()
{
Task task = Task.Run(() =>
{
Console.WriteLine($"线程ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine("Task.Run执行完毕了");
});
await task;
}
这里:
Task.Run(() =>
{
// 任务内容
});
表示创建一个 Task,并执行里面的代码。
例如:
Thread.Sleep(1000);
表示让当前执行任务暂停 1 秒。
最后:
await task;
表示等待这个任务执行完成。
整个过程可以理解为:
Task.Run()
↓
创建任务
↓
执行任务
↓
await 等待任务完成
三、Task.Factory.StartNew()
Task.Factory.StartNew() 也可以用来创建 Task。
示例:
private async Task Demo_TaskFactoryStartNew()
{
Task task = Task.Factory.StartNew(() =>
{
Console.WriteLine($"线程ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine("Task.Factory.StartNew执行完毕了");
});
await task;
}
这里:
Task.Factory.StartNew(() =>
{
// 任务代码
});
会创建一个 Task。
然后:
await task;
等待任务执行完成。
Task.Factory 可以理解为一个用于创建 Task 的工厂对象。
例如:
Task.Factory.StartNew(() =>
{
Console.WriteLine("开始执行");
});
就是通过 TaskFactory 创建一个新的任务。
四、new Task() + Start()
还可以通过 new Task() 手动创建任务。
示例:
private async Task Demo_NewTaskStart()
{
Task task = new Task(() =>
{
Console.WriteLine($"线程ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine("new Task() + Start()执行完毕了");
});
task.Start();
await task;
}
这里:
Task task = new Task(() =>
{
// 任务内容
});
只是创建了一个 Task。
接下来:
task.Start();
才开始执行这个任务。
所以执行过程是:
new Task()
↓
创建 Task
↓
Start()
↓
开始执行任务
↓
await
↓
等待完成
Start() 的作用就是启动已经创建好的 Task。
五、Task.Run() 返回结果
Task 不仅可以执行代码,还可以返回结果。
例如计算:
1 + 2 + 3 + … + 100
代码:
private async Task Demo_TaskRunWithResult()
{
Task<int> task = Task.Run(() =>
{
int sum = 0;
for (int i = 1; i <= 100; i++)
{
sum += i;
}
return sum;
});
int result = await task;
Console.WriteLine($"1~100的和 = {result}");
}
这里:
Task<int>
表示这个 Task 最终会返回一个 int 类型的数据。
任务里面:
return sum;
返回计算结果。
然后:
int result = await task;
就可以获取 Task 返回的结果。
例如:
Task<string>
表示返回一个字符串。
Task<double>
表示返回一个 double。
所以可以记住:
| Task | 没有返回值 |
| Task<int> | 返回 int |
| Task<string> | 返回 string |
六、Task.WhenAll()
Task.WhenAll() 用于等待一组 Task 全部执行完成。
示例:
private async Task Demo_WhenAll()
{
var tasks = new List<Task<int>>();
for (int i = 1; i <= 5; i++)
{
int id = i;
tasks.Add(Task.Run(async () =>
{
await Task.Delay(id * 100);
Console.WriteLine($"任务:{id}–完成了");
return id * 10;
}));
}
int[] result = await Task.WhenAll(tasks);
Console.WriteLine($"所有Task的结果为:[{string.Join(", ", result)}]");
}
首先创建一个集合:
var tasks = new List<Task<int>>();
用于保存多个 Task。
然后循环创建任务:
for (int i = 1; i <= 5; i++)
每次创建一个 Task。
最后:
int[] result = await Task.WhenAll(tasks);
等待集合中的所有任务完成。
因为每个任务都有返回值:
return id * 10;
所以 WhenAll() 最终得到的是:
int[]
例如可能得到:
10
20
30
40
50
因此:
Task.WhenAll()
可以理解成:
等待一组 Task 全部完成,并获取这一组 Task 的结果。
七、Task.WhenAny()
Task.WhenAny() 用于等待一组 Task 中最先完成的任务。
示例:
private async Task Demo_WhenAny()
{
var task1 = Task.Delay(300).ContinueWith(_ => "慢任务");
var task2 = Task.Delay(100).ContinueWith(_ => "快任务");
var task3 = Task.Delay(200).ContinueWith(_ => "中任务");
Task first = await Task.WhenAny(task1, task2, task3);
Console.WriteLine($"最先完成的是: {await first}");
}
这里创建了三个任务:
- task1
- task2
- task3
它们分别延迟:
| task1 | 300 毫秒 |
| task2 | 100 毫秒 |
| task3 | 200 毫秒 |
然后:
Task first = await Task.WhenAny(task1, task2, task3);
会等待最先完成的 Task。
由于 task2 只需要 100 毫秒,所以 task2 会最先完成。
最后:
await first
获取这个最先完成任务的结果。
八、ContinueWith()
ContinueWith() 用于在一个 Task 执行完成之后继续执行代码。
示例:
private async Task Demo_ContinueWith()
{
Task<int> task = Task.Run(() => 42);
Task continuation = task.ContinueWith(t =>
{
Console.WriteLine($"前一个任务的结果:{t.Result}");
Console.WriteLine("Continuation执行完毕");
});
await continuation;
}
首先:
Task<int> task = Task.Run(() => 42);
创建一个返回 42 的 Task。
然后:
task.ContinueWith(...)
表示:
等前面的 Task 执行完成之后,再执行这里面的代码。
这里的:
t
代表前面执行完成的 Task。
因此:
t.Result
可以获取前面 Task 的返回结果。
执行流程:
Task.Run()
↓
返回 42
↓
ContinueWith()
↓
获取 42
↓
执行后续代码
九、Task.Factory.ContinueWhenAll()
ContinueWhenAll() 用于等待一组任务全部完成之后,再执行后续代码。
示例:
private async Task Demo_ContinueWhenAll()
{
Task t1 = Task.Delay(100);
Task t2 = Task.Delay(200);
Task continuation = Task.Factory.ContinueWhenAll(
new[] { t1, t2 },
tasks => Console.WriteLine("ContinueWhenAll所有前置任务已完成")
);
await continuation;
}
这里有两个 Task:
Task t1 = Task.Delay(100);
Task t2 = Task.Delay(200);
然后:
Task.Factory.ContinueWhenAll(
new[] { t1, t2 },
tasks => ...
);
把两个 Task 放进去。
只有 t1 和 t2 都完成之后,下面的代码才会执行:
Console.WriteLine("ContinueWhenAll所有前置任务已完成");
这里的:
tasks
表示前面传入的这一组 Task。
十、TaskCreationOptions.LongRunning
TaskCreationOptions.LongRunning 用于标记一个需要长时间运行的任务。
示例:
private async Task Demo_LongRunning()
{
Task task = Task.Factory.StartNew(() =>
{
Console.WriteLine($"线程ID:{Thread.CurrentThread.ManagedThreadId} 独立线程");
Thread.Sleep(2000);
Console.WriteLine("LongRunning任务执行完毕");
}, TaskCreationOptions.LongRunning);
await task;
}
这里:
TaskCreationOptions.LongRunning
表示告诉 Task 调度器:
这个任务预计会运行较长时间。
例如:
Thread.Sleep(2000);
这里让任务持续运行 2 秒。
通过:
Task.Factory.StartNew(
() =>
{
// 长时间任务
},
TaskCreationOptions.LongRunning);
就可以创建一个标记为 LongRunning 的 Task。
需要注意,LongRunning 是一个任务创建选项,它是给调度器提供的信息,并不是简单地理解为“必定创建一个新线程”。
十一、CancellationToken
在程序运行过程中,有时候需要取消正在执行的任务。
例如:
任务开始
↓
执行第 1 步
↓
执行第 2 步
↓
执行第 3 步
↓
收到取消请求
↓
停止任务
这时候可以使用:
CancellationTokenSource
和:
CancellationToken
示例:
private async Task Demo_CancellationToken()
{
var cts = new CancellationTokenSource();
Task task = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
cts.Token.ThrowIfCancellationRequested();//或者将这段代码注释掉
Console.WriteLine($"执行了第:{i + 1}步");
Thread.Sleep(100);
}
}, cts.Token);
await Task.Delay(350);//如果是1000也就是1秒,才不会卡。这里意思就是0.35秒之后进入到cts.Cancel()里使 cts.Token让整个线程停止
cts.Cancel();
try
{
await task;
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"任务被取消了:{ex.Message}");
}
}
首先:
var cts = new CancellationTokenSource();
创建一个取消控制器。
然后:
cts.Token
获取 Token。
任务执行过程中:
cts.Token.ThrowIfCancellationRequested();
检查有没有收到取消请求。
如果已经调用:
cts.Cancel();
那么:
ThrowIfCancellationRequested()
就会抛出:
OperationCanceledException
所以代码使用:
try
{
await task;
}
catch (OperationCanceledException)
{
// 处理取消
}
捕获取消异常。
十二、CancellationToken 的执行流程
整个过程可以理解为:
CancellationTokenSource
↓
Token
↓
Task
↓
检查 Token
↓
调用 cts.Cancel()
↓
发现取消请求
↓
ThrowIfCancellationRequested()
↓
抛出 OperationCanceledException
需要注意:
cts.Cancel();
主要是发送一个“取消请求”。
它不是直接强制杀死正在运行的代码。
任务需要主动检查 Token:
cts.Token.ThrowIfCancellationRequested();
才能及时发现取消请求。
十三、完整示例入口
如果想把上面的 Demo 全部测试一遍,可以在 WinForms 按钮事件中这样调用:
private async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("=================1.Task.Run=================");
await Demo_TaskRun();
Console.WriteLine("=================2.Task.Factory.StartNew=================");
await Demo_TaskFactoryStartNew();
Console.WriteLine("=================3.new Task() + Start()=================");
await Demo_NewTaskStart();
Console.WriteLine("=================4.Task.Run带返回值=================");
await Demo_TaskRunWithResult();
Console.WriteLine("=================5.Task.WhenAll=================");
await Demo_WhenAll();
Console.WriteLine("=================6.Task.WhenAny=================");
await Demo_WhenAny();
Console.WriteLine("=================7.ContinueWith=================");
await Demo_ContinueWith();
Console.WriteLine("=================8.ContinueWhenAll=================");
await Demo_ContinueWhenAll();
Console.WriteLine("=================9.LongRunning=================");
await Demo_LongRunning();
Console.WriteLine("=================10.CancellationToken=================");
await Demo_CancellationToken();
}
点击按钮后,就会按照顺序执行每一个 Demo。
winform中查看输出的操作:

输出的内容:

网硕互联帮助中心





评论前必须登录!
注册