本课学习目标
- 理解同步执行与异步执行的区别。
- 理解为什么长时间阻塞 UI 线程会让窗体失去响应。
- 认识 Task 与 Task(Of T)。
- 使用 Async 声明异步方法。
- 使用 Await 等待 Task。
- 使用 Task.Delay() 模拟异步等待。
- 理解为什么一般方法应优先使用 Async Function 而不是 Async Sub。
- 在 Windows Forms Click 事件中使用 Async Sub。
- 使用 SqlConnection.OpenAsync()。
- 使用 ExecuteReaderAsync() 与 ReadAsync()。
- 完成 StudentAsyncLoader2026。
32.1 同步与异步有什么不同?
当前方法一直占着执行流程等待结果。若发生在 UI 线程,窗体可能无法及时处理重绘、移动和点击消息。
遇到尚未完成的 Await 时,方法可以暂时让出控制;Task 完成后,再从 Await 后面继续。
32.2 为什么 Windows Forms 会“卡住”?
例如:
Private Sub btnStart_Click(
sender As Object,
e As EventArgs) Handles btnStart.Click
Threading.Thread.Sleep(5000)
lblStatus.Text =
"完成"
End Sub
Thread.Sleep 会让执行这个事件的线程睡眠 5 秒。如果这是 UI 线程,期间窗体无法正常处理其他 UI 工作。
32.3 第一个 Async/Await 示例
Private Async Sub btnStart_Click(
sender As Object,
e As EventArgs) Handles btnStart.Click
lblStatus.Text =
"处理中..."
Await Task.Delay(5000)
lblStatus.Text =
"完成"
End Sub
等待的 5 秒内,窗体仍然可以继续处理 UI 消息。
32.4 Await 时发生了什么?
32.5 什么是 Task?
Task 表示一个可能尚未完成的异步操作。
32.6 Async Function 返回 Task
Private Async Function WaitForMessageAsync() As Task
Await Task.Delay(2000)
End Function
调用:
Await WaitForMessageAsync()
32.7 Async Function 返回 Task(Of T)
Private Async Function GetMessageAsync() As Task(Of String)
Await Task.Delay(1000)
Return "异步操作完成。"
End Function
调用:
Dim message As String =
Await GetMessageAsync()
lblStatus.Text =
message
32.8 为什么 Async Sub 要谨慎使用?
Windows Forms 事件处理器的签名本来就是 Sub,因此可以写:
Private Async Sub btnLoad_Click(
sender As Object,
e As EventArgs) Handles btnLoad.Click
但一般可等待的方法,更建议:
Private Async Function LoadDataAsync() As Task
32.9 Await 会取得异步结果
如果方法返回:
Task(Of Integer)
那么:
Dim count As Integer =
Await GetStudentCountAsync()
Await 完成后,count 直接得到 Integer 结果。
32.10 Async/Await 与异常处理
Await 可以继续配合熟悉的 Try...Catch:
Try
Dim students =
Await LoadStudentsAsync()
Catch ex As Exception
MessageBox.Show(
ex.Message)
End Try
异步操作失败时,异常会在 Await 处重新表现出来,因此可以用正常的 Try...Catch 处理。
32.11 异步操作期间禁用按钮
btnLoad.Enabled =
False
Try
Await LoadDataAsync()
Finally
btnLoad.Enabled =
True
End Try
这样可以防止用户在同一操作尚未完成时重复点击。
32.12 把 Repository 改成异步读取
建立:
Public Async Function GetAllAsync() _
As Task(Of List(Of Student))
完整方法:
Public Async Function GetAllAsync() _
As Task(Of List(Of Student))
Const sql As String =
"SELECT StudentID, Name, Course, Score " &
"FROM dbo.Students " &
"ORDER BY StudentID;"
Dim students As New List(Of Student)()
Using connection As New SqlConnection(
DatabaseHelper.ConnectionString)
Using command As New SqlCommand(
sql,
connection)
Await connection.OpenAsync()
Using reader As SqlDataReader =
Await command.ExecuteReaderAsync()
While Await reader.ReadAsync()
students.Add(
New Student(
reader.GetInt32(0),
reader.GetString(1),
reader.GetString(2),
reader.GetDecimal(3)))
End While
End Using
End Using
End Using
Return students
End Function
32.13 OpenAsync()
Await connection.OpenAsync()
它是打开 SQL Server 连接的异步版本。程序不需要在等待数据库连接建立期间阻塞当前调用流程。
32.14 ExecuteReaderAsync()
Using reader As SqlDataReader =
Await command.ExecuteReaderAsync()
这会异步执行 SELECT,并在完成后得到 SqlDataReader。
32.15 ReadAsync()
While Await reader.ReadAsync()
' 读取当前记录
End While
ReadAsync 是 Read 的异步版本,用来前进到下一笔记录。
32.16 Service 也返回 Task
Public Class StudentService
Private ReadOnly repository As StudentRepository
Public Sub New(
repository As StudentRepository)
Me.repository =
repository
End Sub
Public Async Function GetStudentsAsync() _
As Task(Of List(Of Student))
Return Await repository.GetAllAsync()
End Function
End Class
32.17 Form 异步加载数据
Private Async Function LoadStudentsAsync() As Task
Dim students As List(Of Student) =
Await studentService.GetStudentsAsync()
dgvStudents.DataSource =
Nothing
dgvStudents.DataSource =
students
lblStatus.Text =
$"已加载 {students.Count} 位学生。"
End Function
32.18 异步加载按钮
Private Async Sub btnLoad_Click(
sender As Object,
e As EventArgs) Handles btnLoad.Click
btnLoad.Enabled =
False
lblStatus.Text =
"正在读取数据库..."
Try
Await LoadStudentsAsync()
Catch ex As SqlException
MessageBox.Show(
ex.Message,
"数据库错误",
MessageBoxButtons.OK,
MessageBoxIcon.Error)
lblStatus.Text =
"读取失败。"
Finally
btnLoad.Enabled =
True
End Try
End Sub
32.19 实作:Student Async Loader 2026
建立:
StudentAsyncLoader2026
| Student ID | 姓名 | 课程 | 成绩 |
|---|---|---|---|
| 1 | 李明 | Visual Basic 2026 | 88.00 |
| 3 | 陈伟 | Python Programming | 91.00 |
32.20 用 Task.Delay 观察 UI 响应
Private Async Sub btnSimulate_Click(
sender As Object,
e As EventArgs) Handles btnSimulate.Click
btnSimulate.Enabled =
False
lblStatus.Text =
"模拟耗时工作..."
Try
Await Task.Delay(3000)
lblStatus.Text =
"模拟工作完成。"
Finally
btnSimulate.Enabled =
True
End Try
End Sub
运行后,在等待期间尝试移动窗体,你会发现窗体仍能响应。
32.21 不要把所有工作都丢给 Task.Run
对于本来就提供异步 API 的 I/O 操作,例如:
OpenAsync()
ExecuteReaderAsync()
ReadAsync()
优先直接 Await 它们,而不是:
Await Task.Run(
Sub()
connection.Open()
End Sub)
32.22 I/O-bound 与 CPU-bound
数据库、网络、文件等待。优先使用 API 自带的 Async 方法并 Await。
大量计算、图像处理等真正持续占用 CPU 的工作,才可能考虑 Task.Run 把计算移出 UI 线程。
32.23 一个 Async 方法可以有多个 Await
Private Async Function DemoAsync() As Task
lblStatus.Text =
"步骤 1"
Await Task.Delay(1000)
lblStatus.Text =
"步骤 2"
Await Task.Delay(1000)
lblStatus.Text =
"完成"
End Function
32.24 Task.WhenAll 简介
如果几个异步操作彼此独立,可以先启动,再一起等待:
Dim task1 As Task =
Task.Delay(1500)
Dim task2 As Task =
Task.Delay(2000)
Await Task.WhenAll(
task1,
task2)
这为以后同时调用多个 Web API 或并行等待多个 I/O 任务打下基础。
32.25 Async Sub 的异常为什么更难管理?
因为 Async Sub 不返回 Task,调用者无法这样写:
Await SomeAsyncSub()
因此一般逻辑应写成:
Private Async Function SaveAsync() As Task
而事件处理器只是最外层入口:
Private Async Sub btnSave_Click(...) _
Handles btnSave.Click
Await SaveAsync()
End Sub
32.26 初学者常见错误
方法里有 Await 却没有 Async
包含 Await 的方法需要使用 Async 修饰符。
一般方法全部写 Async Sub
除事件处理器外,通常应返回 Task 或 Task(Of T)。
用 Thread.Sleep 模拟异步
Sleep 阻塞线程;Task.Delay 才适合本课的异步等待演示。
忘记 Await
调用 Async 方法却不 Await,后续代码可能在操作尚未完成时就继续。
32.27 异步数据库读取流程
事件处理器是 Async Sub。
Form 等待 Task(Of List(Of Student))。
Repository 异步建立数据库连接。
异步等待数据库结果。
绑定 DataGridView 并恢复按钮。
32.28 小练习
- 把 Task.Delay 从 3 秒改成 8 秒,观察窗体是否仍能移动。
- 写一个 GetStudentCountAsync(),返回 Task(Of Integer)。
- 把 Repository 的 INSERT 改成 ExecuteNonQueryAsync()。
- 新增“异步新增学生”按钮,并在操作期间禁用按钮。
- 使用 Try...Catch 捕获异步数据库异常。
- 建立两个 Task.Delay,并用 Task.WhenAll 等待。
- 解释为什么数据库 Async API 不需要再包在 Task.Run 中。
本课复习
- 同步等待为什么可能冻结 Windows Forms UI?
- Task 表示什么?
- Task(Of T) 与 Task 有什么不同?
- Async 的作用是什么?
- Await 的作用是什么?
- 为什么 Task.Delay 比 Thread.Sleep 更适合异步等待?
- 为什么事件处理器可以用 Async Sub,但一般方法优先 Async Function?
- OpenAsync() 做什么?
- ExecuteReaderAsync() 与 ReadAsync() 分别做什么?
- I/O-bound 与 CPU-bound 有什么基本区别?