C#多线程(15):任务基础③
痴者工良 人气:1
[TOC]
任务基础一共三篇,本篇是第三篇,之后开始学习异步编程、并发、异步I/O的知识。
本篇会继续讲述 Task 的一些 API 和常用的操作。
### TaskAwaiter
先说一下 `TaskAwaiter`,`TaskAwaiter` 表示等待异步任务完成的对象并为结果提供参数。
Task 有个 `GetAwaiter()` 方法,会返回`TaskAwaiter` 或`TaskAwaiter`,`TaskAwaiter` 类型在 `System.Runtime.CompilerServices` 命名空间中定义。
`TaskAwaiter` 类型的属性和方法如下:
属性:
| 属性 | 说明 |
| ----------- | ---------------------------------------- |
| IsCompleted | 获取一个值,该值指示异步任务是否已完成。 |
方法:
| 方法 | 说明 |
| ------------------------- | ----------------------------------------------------------- |
| GetResult() | 结束异步任务完成的等待。 |
| OnCompleted(Action) | 将操作设置为当 TaskAwaiter 对象停止等待异步任务完成时执行。 |
| UnsafeOnCompleted(Action) | 计划与此 awaiter 相关异步任务的延续操作。 |
使用示例如下:
```csharp
static void Main()
{
Task task = new Task(()=>
{
Console.WriteLine("我是前驱任务");
Thread.Sleep(TimeSpan.FromSeconds(1));
return 666;
});
TaskAwaiter awaiter = task.GetAwaiter();
awaiter.OnCompleted(()=>
{
Console.WriteLine("前驱任务完成时,我就会继续执行");
});
task.Start();
Console.ReadKey();
}
```
另外,我们前面提到过,任务发生未经处理的异常,任务被终止,也算完成任务。
### 延续的另一种方法
上一节我们介绍了 `.ContinueWith()` 方法来实现延续,这里我们介绍另一个延续方法 `.ConfigureAwait()`。
`.ConfigureAwait()` 如果要尝试将延续任务封送回原始上下文,则为 `true`;否则为 `false`。
我来解释一下, `.ContinueWith()` 延续的任务,当前驱任务完成后,延续任务会继续在此线程上继续执行。这种方式是同步的,前者和后者连续在一个线程上运行。
` .ConfigureAwait(false)` 方法可以实现异步,前驱方法完成后,可以不理会后续任务,而且后续任务可以在任意一个线程上运行。这个特性在 UI 界面程序上特别有用。
可以参考:[https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f](https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f)
其使用方法如下:
```csharp
static void Main()
{
Task task = new Task(()=>
{
Console.WriteLine("我是前驱任务");
Thread.Sleep(TimeSpan.FromSeconds(1));
return 666;
});
ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter = task.ConfigureAwait(false).GetAwaiter();
awaiter.OnCompleted(()=>
{
Console.WriteLine("前驱任务完成时,我就会继续执行");
});
task.Start();
Console.ReadKey();
}
```
`ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ` 拥有跟 `TaskAwaiter` 一样的属性和方法。
`.ContinueWith()` 跟 ` .ConfigureAwait(false)` 还有一个区别就是 前者可以延续多个任务和延续任务的任务(多层)。后者只能延续一层任务(一层可以有多个任务)。
### 另一种创建任务的方法
前面提到提到过,创建任务的三种方法:`new Task()`、`Task.Run()`、`Task.Factory.SatrtNew()`,现在来学习第四种方法:`TaskCompletionSource` 类型。
我们来看看 `TaskCompletionSource` 类型的属性和方法:
属性:
| 属性 | 说明 |
| ---- | ------------------------------------------- |
| Task | 获取由此 Task 创建的 TaskCompletionSource。 |
方法:
| 方法 | 说明 |
| --------------------------------- | ------------------------------------------------------------ |
| SetCanceled() | 将基础 Task 转换为 Canceled 状态。 |
| SetException(Exception) | 将基础 Task 转换为 Faulted 状态,并将其绑定到一个指定异常上。 |
| SetException(IEnumerable) | 将基础 Task 转换为 Faulted 状态,并对其绑定一些异常对象。 |
| SetResult(TResult) | 将基础 Task 转换为 RanToCompletion 状态。 |
| TrySetCanceled() | 尝试将基础 Task 转换为 Canceled 状态。 |
| TrySetCanceled(CancellationToken) | 尝试将基础 Task 转换为 Canceled 状态并启用要存储在取消的任务中的取消标记。 |
| TrySetException(Exception) | 尝试将基础 Task 转换为 Faulted 状态,并将其绑定到一个指定异常上。 |
| TrySetException(IEnumerable) | 尝试将基础 Task 转换为 Faulted 状态,并对其绑定一些异常对象。 |
| TrySetResult(TResult) | 尝试将基础 Task 转换为 RanToCompletion 状态。 |
`TaskCompletionSource` 类可以对任务的生命周期做控制。
首先要通过 `.Task` 属性,获得一个 `Task` 或 `Task` 。
```csharp
TaskCompletionSource task = new TaskCompletionSource();
Task myTask = task.Task; // Task myTask = task.Task;
```
然后通过 `task.xxx()` 方法来控制 `myTask` 的生命周期,但是呢,myTask 本身是没有任务内容的。
使用示例如下:
```csharp
static void Main()
{
TaskCompletionSource task = new TaskCompletionSource();
Task myTask = task.Task; // task 控制 myTask
// 新开一个任务做实验
Task mainTask = new Task(() =>
{
Console.WriteLine("我可以控制 myTask 任务");
Console.WriteLine("按下任意键,我让 myTask 任务立即完成");
Console.ReadKey();
task.SetResult(666);
});
mainTask.Start();
Console.WriteLine("开始等待 myTask 返回结果");
Console.WriteLine(myTask.Result);
Console.WriteLine("结束");
Console.ReadKey();
}
```
其它例如 `SetException(Exception)` 等方法,可以自行探索,这里就不再赘述。
参考资料:[https:/https://img.qb5200.com/download-x/devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/](https:/https://img.qb5200.com/download-x/devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/)
这篇文章讲得不错,而且有图:[https://gigi.nullneuron.net/gigilabs/taskcompletionsource-by-example/](https://gigi.nullneuron.net/gigilabs/taskcompletionsource-by-example/)
### 实现一个支持同步和异步任务的类型
这部分内容对 `TaskCompletionSource` 继续进行讲解。
这里我们来设计一个类似 Task 类型的类,支持同步和异步任务。
- 用户可以使用 `GetResult()` 同步获取结果;
- 用户可以使用 `RunAsync()` 执行任务,使用 `.Result` 属性异步获取结果;
其实现如下:
```csharp
///
/// 实现同步任务和异步任务的类型
///
///
public class MyTaskClass
{
private readonly TaskCompletionSource source = new TaskCompletionSource();
private Task task;
// 保存用户需要执行的任务
private Func _func;
// 是否已经执行完成,同步或异步执行都行
private bool isCompleted = false;
// 任务执行结果
private TResult _result;
///
/// 获取执行结果
///
public TResult Result
{
get
{
if (isCompleted)
return _result;
else return task.Result;
}
}
public MyTaskClass(Func func)
{
_func = func;
task = source.Task;
}
///
/// 同步方法获取结果
///
///
public TResult GetResult()
{
_result = _func.Invoke();
isCompleted = true;
return _result;
}
///
/// 异步执行任务
///
public void RunAsync()
{
Task.Factory.StartNew(() =>
{
source.SetResult(_func.Invoke());
isCompleted = true;
});
}
}
```
我们在 Main 方法中,创建任务示例:
```csharp
class Program
{
static void Main()
{
// 实例化任务类
MyTaskClass myTask1 = new MyTaskClass(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(1));
return "www.whuanle.cn";
});
// 直接同步获取结果
Console.WriteLine(myTask1.GetResult());
// 实例化任务类
MyTaskClass myTask2 = new MyTaskClass(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(1));
return "www.whuanle.cn";
});
// 异步获取结果
myTask2.RunAsync();
Console.WriteLine(myTask2.Result);
Console.ReadKey();
}
}
```
### Task.FromCanceled()
微软文档解释:创建 Task,它因指定的取消标记进行的取消操作而完成。
这里笔者抄来了一个[示例](https://stackoverflow.com/questions/25510766/how-to-create-a-cancelled-task):
```csharp
var token = new CancellationToken(true);
Task task = Task.FromCanceled(token);
Task genericTask = Task.FromCanceled(token);
```
网上很多这样的示例,但是,这个东西到底用来干嘛的?new 就行了?
带着疑问我们来探究一下,来个示例:
```csharp
public static Task Test()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
return Task.FromCanceled
加载全部内容