云计算百科
云计算领域专业知识百科平台

C#开发与学习中常用的Task方法及与Thread的基础对比!!!

一、前言

在 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中查看输出的操作:
在这里插入图片描述
输出的内容:
在这里插入图片描述

赞(0)
未经允许不得转载:网硕互联帮助中心 » C#开发与学习中常用的Task方法及与Thread的基础对比!!!
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!