1. 异步编程的典型场合
2. 异步方法格式
异步方法旨在成为非阻止操作,异步方法中的 await 表达式在等待的任务正在运行时不会阻止当前线程。
C# 中的 Async 和 Await 关键字是异步编程的核心。
async 和 await 关键字不会创建其他线程。 因为异步方法不会在其自身线程上运行,因此它不需要多线程。
public async Task<int> GetUrlContentLengthAsync()
{
var client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://learn.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
-
方法签名包含 async 修饰符。
-
按照约定,异步方法的名称以“Async”后缀结尾。(例GetUrlContentLengthAsync)
-
返回类型为下列类型之一:
Task<TResult>如果你的方法有操作数为 TResult 类型的返回语句 Task:如果你的方法没有返回语句或具有没有操作数的返回语句 void:如果要编写异步事件处理程序 具有 GetAwaiter 方法的任何其他类型。
异步方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控件返回到方法的调用方(例如winform界面可以响应其它操作)。
3. 异步方法的运行机制
-
1 ) 调用方法Calling method 等待 GetUrlContentLengthAsync 异步方法。
-
2 ) GetUrlContentLengthAsync 创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。
-
3 ) GetStringAsync 中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync 会将控制权出让给其调用方 GetUrlContentLengthAsync。
GetStringAsync 返回 Task<string>,并且 GetUrlContentLengthAsync 将Task分配给 getStringTask 变量。 该Task表示调用 GetStringAsync 的正在进行的进程,其中承诺当工作完成时产生实际字符串值。
-
4 ) 由于尚未等待 getStringTask,因此,GetUrlContentLengthAsync 可以继续执行不依赖于 GetStringAsync 得出的最终结果的其他工作。
-
5 ) DoIndependentWork
-
6 ) GetUrlContentLengthAsync 已运行完毕,可以不受 getStringTask 的结果影响。 接下来,GetUrlContentLengthAsync 需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。因此,GetUrlContentLengthAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 GetUrlContentLengthAsync 的方法。 GetUrlContentLengthAsync 将 Task 返回给调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。
在Calling method中,处理模式会继续。 在等待结果前,调用方可以开展不依赖于 GetUrlContentLengthAsync 结果的其他工作,否则就需等待片刻。 调用方法等待 GetUrlContentLengthAsync,而 GetUrlContentLengthAsync 等待 GetStringAsync。 -
7 ) GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用 GetStringAsync 所返回的。 (记住,该方法已返回步骤 3 中的一个任务)。相反,字符串结果存储在表示 getStringTask 方法完成的任务中。 await 运算符从 getStringTask 中检索结果
-
8 ) 当 GetUrlContentLengthAsync 具有字符串结果时,该方法可以计算字符串长度。 然后,GetUrlContentLengthAsync 工作也将完成,并且等待事件处理程序可继续使用。
示例 (winform界面)
private async void button11_Click(object sender, EventArgs e)
{
var task1 = M1Async();//不会阻塞
richTextBox1.Text += $"----AAA\n";
richTextBox1.Text += $"----M3()执行----:end{M3()}\n";//同步方法
richTextBox1.Text += $"等待task1...(3秒)\n";
var s1 = await task1;//有返回值
richTextBox1.Text += $"----task1完成----end:{s1}\n";
var task2 = M2Async();//不会阻塞
richTextBox1.Text += $"----BBB\n";
richTextBox1.Text += $"等待task2...(3秒)\n";
await task2;//无返回值
richTextBox1.Text += $"----task2完成\n";
}
async Task<string> M1Async()
{
await Task.Delay(3000);
return "M1Async end...";
}
async Task M2Async()
{
await Task.Delay(3000);
}
/*输出=>
----AAA
----M3()执行----end:M3...
等待task1...(3秒)
----task1完成----:M1Async end...
----BBB
等待task2...(3秒)
----task2完成
*/
因为是异步方法,await将控制权出让给button11_Click的调用方Form,界面可以进行其它交互操作。
如果M1Async()是同步方法,此处阻塞,界面点不动,等待M1Async()返回)
//小结:CallingMethod 要标记async,执行到…Async方法时不会阻塞等待其返回结果,而是立即继续执行下一段,直到await,等待…Async方法返回,而且此时控制权交给CallingMethod的调用方。总之尽量不让…Async方法阻塞CallingMethod调用方。
4. await
- await 指定暂停点,(如果await对象还没完成操作)暂停所属的 async 方法,控制权让给其调用方。 异步操作完成后,await 运算符将返回操作(await对象异步操作)的结果(如果有)。
- 当 await 运算符应用到表示已完成操作的操作数时(运行到await处时其对象已完成操作),它将立即返回操作的结果,而不会暂停其所属的方法。
- await 运算符不会阻止计算异步方法的线程。 当 await 运算符暂停其所属的异步方法时,控件将返回到方法的调用方。
- 异步方法在 await 表达式执行时暂停并不构成方法退出,只会导致 finally 代码块不运行。
- await t 的操作数 t 通常是:
Task、Task<TResult>、ValueTask 或 ValueTask<TResult>
。如果表达式 t 的类型为Task<TResult>
或ValueTask<TResult>
,则表达式 await t 的类型为 TResult。 如果 t 的类型为 Task 或 ValueTask,则 await t 的类型为 void。
5. 返回类型和参数
异步方法通常返回 Task 或 Task <TResult>
。
如果方法包含指定 TResult 类型操作数的 return 语句,将 Task 指定为返回类型。
如果方法不含任何 return 语句或包含不返回操作数的 return 语句,则将 Task 用作返回类型。
6.BeginInvoke(Delegate)
命名空间: System.Windows.Forms
Invoke 在拥有此控件的基础窗口句柄的线程上执行指定的委托。
BeginInvoke 在创建控件的基础句柄所在线程上异步执行指定委托。