第八章介绍了 C# 中可用异步编程的实践和解决方案,还讨论了何时适合使用异步编程等。本章主要介绍 async 和 await 关键字。
其实在之前的学习中,大家都已经了解过这两个关键字了,用得非常多。其实我觉得没有必要再赘述了,不过这里还是简单地看一看吧。
本教程学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode
因为篇幅限制,本篇为上篇,主要内容为 async 和 await 关键字 和手动实现 TAP。
1、关于 async 和 await 关键字
使用 async 和 await 关键字,代码可以保持同步实现中的状态,仅需要做出很小的改变即可。
1.1、基于事件的异步模式
最原始的写法是基于事件的异步模式(Event-Based Asynchronous Pattern , EAP),需要通过注册回调来实现异步操作。代码示例如下:
public static void WebClientSample()
{
Debug.Log("WebClientSample ,Start !");
WebClient client = new WebClient();
client.DownloadStringCompleted += OnWebClientDownloaded;
client.DownloadStringAsync(new Uri("https://blog.csdn.net/cyf649669121"));
Debug.Log("WebClientSample ,开始下载!");
}
private static void OnWebClientDownloaded(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
Debug.LogError($"下载失败 : {e.Error.Message}");
return;
}
Debug.Log("下载成功");
Debug.Log(e.Result);
}
这一段代码运行结果如下:
可以看到确实是实现了异步操作,但是是因为基于事件的写法,我们将代码拆成了两部分。在某些情况下,这种写法很方便,也符合逻辑。但是当我们的异步操作是流程性质的,这样的写法就比较繁琐:我们需要将方法进行串联以实现流程。
1.2、基于任务的异步模式
上述代码,我们将其改造成基于任务的异步模式:
private void RunWithWebClientInTask()
{
WebClientInTask();
Debug.Log("RunWithWebClientInTask End !");
}
public static async Task WebClientInTask()
{
Debug.Log("开始异步任务下载!");
WebClient client = new WebClient();
var result = await client.DownloadStringTaskAsync(new Uri("https://blog.csdn.net/cyf649669121"));
Debug.Log("下载成功");
Debug.Log(result);
}
使用 async 和 await 关键字的写法,和 EAP 模式显而易见。不过我倒不觉得这两者存在绝对的优劣,完全看各自的使用场景自行选择。
执行结果如下:
可见仍然没有阻塞主线程,同样实现了异步。当我们的方法包含 async 关键字时,就是给编译器指令,表示该方法将在必要的时候(await)异步执行。
1.3、异步方法返回的类型
异步方法有3种返回类型:void、Task、Task<T>。所有异步方法必须返回 Task 才能等待(使用 await 关键字),调用时不会立即返回,而是等待异步执行。void 方法则表示不想等待的情况,调用方法不会阻塞。
这里写个示例:
public static async void WaitWithoutTask()
{
Debug.Log("WaitWithoutTask Start !");
await Task.Delay(1000);
Debug.Log("WaitWithoutTask End !");
}
public static async Task WaitWithTask()
{
Debug.Log("WaitWithTask Start !");
await Task.Delay(1000);
Debug.Log("WaitWithTask End !");
}
这两个代码就返回值有区别,但是使用时区别就是返回 Task 时可以选择是否等待执行完成(使用 await),而返回 void 时不能等待。
2、手动实现 TAP
其实在 第二章:任务并行性 中已经讨论了如何使用 Task 类实现基于任务的异步模式(Task-Based Asynchronous Parrern,TAP)。使用 async 关键字时,编译器将执行所需优化,非常方便。这里我们介绍下如果手动实现 TAP。
TaskCompletionSource表示未绑定到委托的 Task<TResult> 的制造者方,并通过 Task 属性提供对使用者方的访问。https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.taskcompletionsource-1?view=netstandard-2.1 这里我们会用到 TaskCompletionSource<T> 这个类:
public static Task<int> MyTaskResult()
{
TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
Thread.Sleep(1000);//会阻塞调用线程
taskCompletionSource.SetResult(555);
return taskCompletionSource.Task;
}
就从写法来看,不如直接用 Task,而且也没有什么别的优势。
(未完待续)