对 C# 的关键字 async 和 await 的理解
对 C# 的关键字 async 和 await 的理解
1. 异步和同步
async,单词原意:异步。
在多线程编程中,通俗地讲,异步就是:在当前线程之外,另开一个线程,以执行一个相对独立的任务;当前线程不管新开线程是否执行完毕,继续执行自身任务或结束自身。
相反地,也是通俗地讲,同步就是:当前线程等待新开线程执行完毕,再继续执行自身任务【一个等待另一个的结束,在它结束之后,继续自身】。
背景知识点
- :
- 异步编程模型 (APM,Asynchronous Programming Model) ;
- 基于事件的异步模式 (EAP,Event-based Asynchronous Pattern)
- 基于任务的异步模式 (TAP, Task-based Asynchronous Pattern)
2. 限制条件
- 被关键字 async 标记的函数,返回类型只能是: void、Task 或 Task
、类似任务的类型、IAsyncEnumerable 或 IAsyncEnumerator 。 - 被关键字 async 标记的函数,其【实现】需包含关键字 await (否则会出现警告,注意,并不是错误)。
3. 意义
一个函数,如果被关键字 async 标记,则意味着这个函数可能存在【具有异步的能力的】代码(做到了另开线程以完成某一任务的能力)。这些代码被关键字 await 标记出来。
调用者可以选择使用它的异步能力,或者放弃它的异步能力而改为同步执行(但保留了异步能力,第三方调用【调用者】时,仍然有同样的选择权利)
对于调用者,函数的异步能力,有时却会是一个大困扰,await标记的调用,避免了这样的麻烦。
4. 用法
以下代码示例,演示了一个函数通过 Http 请求以执行对指定临时数据的删除工作。它被关键字 async 标记,并且其【实现】包含了关键字 await 。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
...
// 被关键字 async 标记的函数
public async Task RemoveDataSetAsync(string deleteURL, string whereClause)
{
if (string.IsNullOrEmpty(whereClause)) { return; }
if (string.IsNullOrEmpty(deleteURL))
{
throw new Exception("deleteURL参数为空!");
}
var handler = new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.None
};
using (var httpclient = new HttpClient(handler))
{
httpclient.BaseAddress = new Uri(deleteURL);
var content = new FormUrlEncodedContent(new Dictionary()
{
{"where", whereClause},
{"f","json" }
});
// 关键字 await 标记的【具有异步的能力的】代码
var response = await httpclient.PostAsync(deleteURL, content);
string responseString = await response.Content.ReadAsStringAsync();
Logger.Info($"DeleteURL返回状态值:{response.StatusCode}");
if (!responseString.Contains("\"success\":true"))
{
throw new Exception($"删除临时库数据失败!{responseString}");
}
}
}
所谓用法,即其他函数以什么样的方式调用它。
方式一
public class DeleteHelper
{
public void Run()
{
Task.Run(new Action(RemoveAsync));
}
public async void RemoveAsync()
{
try
{
string whereClause = "1=1";
string deleteUrl = "http://IP/LAYER/FeatureServer/0/deleteFeatures";
await RemoveDataSetAsync(deleteUrl, whereClause);
} catch { }
}
public async Task RemoveDataSetAsync(string deleteURL, string whereClause)
{
...
}
}
放弃异步能力,采用同步的方式,但为第三方保留了异步的能力。
【方法 RemoveAsync】对【方法 RemoveDataSetAsync】进行了调用。它将自身标记为了 async,并在调用 RemoveDataSetAsync 时,使用了关键字 await。这样的调用方式,正如 RemoveDataSetAsync 对 httpclient.PostAsync 的调用。此时,我们就会有疑问,这样的调用方式,意味着什么呢?
对具有异步能力的函数,使用关键字 await 进行调用它时,意味着放弃了使用它的异步能力,而是等待它另开线程的完成。此示例中,另开线程的工作在 httpclient.PostAsync 方法中提供。
方式二
那么,我们又将如何使用它提供的异步能力呢?请看下面的示例:
public class DeleteHelper
{
public void Run()
{
Task.Run(new Action(RemoveAsync));
}
public void RemoveAsync()
{
try
{
string whereClause = "1=1";
string deleteUrl = "http://IP/LAYER/FeatureServer/0/deleteFeatures";
// RemoveDataSet之前的代码
...
var _ = RemoveDataSetAsync(deleteUrl, whereClause);//放弃使用关键字 await
// RemoveDataSet之后的代码
...
} catch { }
}
public async Task RemoveDataSetAsync(string deleteURL, string whereClause)
{
// 在第一个关键字 await 之前的代码
...
// 关键字 await 标记的【具有异步的能力的】代码
var response = await httpclient.PostAsync(deleteURL, content);
string responseString = await response.Content.ReadAsStringAsync();
...
}
}
在 RemoveAsync 方法中,不使用 关键字 await 对 RemoveDataSetAsync 时进行调用,则使用了 RemoveDataSetAsync 提供的异步能力,即不等待另开线程的执行完毕,而是直接运行了 RemoveDataSet 之后的代码。
此时,我们会有个疑问:具体线程是在什么时候放弃了【等待另开线程的执行完毕】?
答案是,在 RemoveDataSetAsync 方法中 第一个【关键字 await 】之前。即在第一个关键字 await 之前的代码,能被线程同步执行,然后线程立即转至执行 RemoveDataSet 之后的代码。这一点非常违背我们的直观,需要特别注意。如果不注意,就会错误的以为, RemoveDataSet之前的代码执行之后,直接转至执行 RemoveDataSet之后的代码,而忽略了在第一个关键字 await 之前的代码。
5. 总结
使用关键字 async 和 await ,除了对程序提供了性能提升之外,也对程序提供了异步调用能力的传递(被关键字 async 标记的新方法,对旧方法使用 await 标记的调用)。同时,使【代码阅读者】具有了更好的【对已有的具有多线程编程特性的代码】的理解能力。同时,使开发者能更好地、简便地进行多线程编程。
6. main 函数
附上 main 函数,以便大家理解本篇内容:
main 函数所在的主线程,与 RemoveAsync 所在的线程,不是同一个线程,因为进行了 Task.Run(new Action(RemoveAsync)) 调用。而这两个线程,又与 存在于 httpclient.PostAsync 中的线程不同。
internal class Program
{
static void Main(string[] args)
{
try
{
DeleteHelper helper = new DeleteHelper();
helper.Run();
}
catch
{
}
finally
{
//进程保留
Console.ReadLine();
}
}
}
因此,对于Task.Run,甚至可以是
public class DeleteHelper
{
public void Run()
{
Task.Run(async () =>
{
try
{
string whereClause = "1=1";
string deleteUrl = "http://IP/LAYER/FeatureServer/0/deleteFeatures";
await RemoveDataSet(deleteUrl, whereClause);
} catch { }
});
}
public async Task RemoveDataSetAsync(string deleteURL, string whereClause)
{
...
}
}
另,要想一个函数原生具有异步能力,如何做到呢?
可以是以下方法,对返回值是 Task 方法,添加 await 标记,并标记函数为 async
public async void Run()
{
Console.WriteLine("do some work here");
await Task.Run(() =>
{
try
{
Console.WriteLine("do some work here, too");
}
catch { }
});
Console.WriteLine("9.");
}
7. 结束语
如有错误,欢迎指正。