异步、同步与多线程
开发专题 · async/await · Task · Thread(.NET Framework 4.8)
本页说明在 .NET Framework 4.8 窗体项目中区分同步、异步(async/await)与多线程(Thread / Task),以及如何在后台完成后安全更新界面。目标框架以 LCode 默认的 .NET 4.8 为准;示例面向 C# 5.0 与 VB.NET(可用 async/Await,勿用 C# 6+ 语法如字符串插值、nameof)。操作文案以 LCode 中文界面为准。网络场景见 网络请求。
三种写法怎么选
| 方式 | 典型 API | 适用 |
|---|---|---|
| 同步 | 普通方法顺序执行;如 GetResponse()、Thread.Sleep |
极短操作;或已在后台线程中的阻塞调用。勿在 UI 事件里长时间阻塞 |
| 异步(推荐 UI) | async/await + Task(C# 5 / VB Async/Await) |
IO 等待(网络、文件、延时)时不占死 UI 线程;事件处理可用 async void / Async Sub |
| 多线程 | Thread、Task.Run / Task.Factory.StartNew |
CPU 密集或必须阻塞的旧 API;结果回 UI 须 Invoke/BeginInvoke |
在 LCode 中的准备
- 窗体项目默认已引用
System、System.Core;Task、Thread一般无需额外浏览引用。 - C# 文件顶部加
using System.Threading;、using System.Threading.Tasks;;VB 用Imports对应命名空间。 - 菜单 编译 → 编译解决方案(F8),确认无「找不到类型 Task / Thread」。
提示:添加程序集见 引用 DLL。LCode 主界面 NuGet 包管理暂不支持,待更新完善——本页仅用框架类型,不必第三方包。
UI 线程注意
WinForms 控件只能由创建它们的UI 线程直接读写。按钮点击、跨线程完成后改 Text/Enabled 时须遵守下表。
| 场景 | 正确做法 | 常见错误 |
|---|---|---|
| 按钮里耗时 IO | 事件写成 async void / Async Sub,内部 await 异步 API |
在点击处理里直接 Thread.Sleep 或同步网络,窗体假死 |
| 后台线程改控件 | Control.Invoke / BeginInvoke 封送到 UI |
工作线程直接写 label1.Text → 跨线程异常 |
await 之后 |
默认会回到 UI 同步上下文,可直接改控件 | 对未 ConfigureAwait(false) 的任务在 UI 上滥用 .Result / .Wait() → 可能死锁 |
| 库方法签名 | 可等待逻辑返回 Task / Task<T> |
库里到处写 async void(异常难观察;仅事件入口适合) |
async / await(C# 5)
把「等待完成」写成顺序代码,实际不阻塞 UI:遇到 await 时先返回事件循环,任务完成后继续。下面用延时模拟耗时,再更新标签。
C#
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
private async void btnRun_Click(object sender, EventArgs e)
{
btnRun.Enabled = false;
lblStatus.Text = "进行中…";
try
{
string result = await DoWorkAsync();
lblStatus.Text = result;
}
catch (Exception ex)
{
MessageBox.Show("失败: " + ex.Message);
}
finally
{
btnRun.Enabled = true;
}
}
private async Task<string> DoWorkAsync()
{
await Task.Delay(1500);
return "完成(异步)";
}
VB.NET
Imports System.Threading.Tasks
Imports System.Windows.Forms
Private Async Sub btnRun_Click(sender As Object, e As EventArgs) Handles btnRun.Click
btnRun.Enabled = False
lblStatus.Text = "进行中…"
Try
Dim result As String = Await DoWorkAsync()
lblStatus.Text = result
Catch ex As Exception
MessageBox.Show("失败: " & ex.Message)
Finally
btnRun.Enabled = True
End Try
End Sub
Private Async Function DoWorkAsync() As Task(Of String)
Await Task.Delay(1500)
Return "完成(异步)"
End Function
| 成员 / 关键字 | 说明 |
|---|---|
async / Async |
标记方法内可使用 await / Await |
await / Await |
等待 Task 完成;UI 事件中默认续接回 UI 线程 |
Task / Task<T> |
异步操作句柄;库方法优先返回 Task,事件可用 async void |
Task.Delay |
非阻塞延时(勿用 Thread.Sleep 卡 UI) |
ConfigureAwait(false) |
库代码中可不捕获 UI 上下文;窗体事件里一般不必写 |
Task:后台计算再回 UI
CPU 密集或只能同步调用的 API,可用 Task.Run(.NET 4.5+ / 4.8)丢到线程池,再用 await 取结果并更新界面。
C#
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
private async void btnCalc_Click(object sender, EventArgs e)
{
lblStatus.Text = "计算中…";
int n = 40;
long value = await Task.Run(delegate
{
return Fib(n);
});
lblStatus.Text = "Fib(" + n + ") = " + value;
}
private static long Fib(int n)
{
if (n <= 1) return n;
return Fib(n - 1) + Fib(n - 2);
}
VB.NET
Imports System.Threading.Tasks
Imports System.Windows.Forms
Private Async Sub btnCalc_Click(sender As Object, e As EventArgs) Handles btnCalc.Click
lblStatus.Text = "计算中…"
Dim n As Integer = 40
Dim value As Long = Await Task.Run(Function() Fib(n))
lblStatus.Text = "Fib(" & n & ") = " & value
End Sub
Private Shared Function Fib(n As Integer) As Long
If n <= 1 Then Return n
Return Fib(n - 1) + Fib(n - 2)
End Function
| 成员 | 说明 |
|---|---|
Task.Run |
在线程池执行委托;适合短~中等 CPU/阻塞工作 |
Task.Factory.StartNew |
更细调度选项;一般优先 Task.Run |
task.Result / Wait() |
同步等待;勿在 UI 线程对仍需回 UI 的任务使用 |
Task.WhenAll / WhenAny |
组合多个任务 |
CancellationToken |
协作式取消;长时间循环中检查 IsCancellationRequested |
Thread:显式线程 + Invoke
需要单独线程生命周期、或对接只提供线程模型的旧代码时用 Thread。工作线程内改控件必须 Invoke。
C#
using System;
using System.Threading;
using System.Windows.Forms;
private void btnThread_Click(object sender, EventArgs e)
{
btnThread.Enabled = false;
lblStatus.Text = "线程运行中…";
Thread worker = new Thread(WorkerProc);
worker.IsBackground = true;
worker.Start();
}
private void WorkerProc()
{
Thread.Sleep(1500);
string text = "完成(Thread)";
if (lblStatus.InvokeRequired)
{
lblStatus.BeginInvoke(new Action(delegate
{
lblStatus.Text = text;
btnThread.Enabled = true;
}));
}
else
{
lblStatus.Text = text;
btnThread.Enabled = true;
}
}
VB.NET
Imports System.Threading
Imports System.Windows.Forms
Private Sub btnThread_Click(sender As Object, e As EventArgs) Handles btnThread.Click
btnThread.Enabled = False
lblStatus.Text = "线程运行中…"
Dim worker As New Thread(AddressOf WorkerProc)
worker.IsBackground = True
worker.Start()
End Sub
Private Sub WorkerProc()
Thread.Sleep(1500)
Dim text As String = "完成(Thread)"
If lblStatus.InvokeRequired Then
lblStatus.BeginInvoke(New Action(Sub()
lblStatus.Text = text
btnThread.Enabled = True
End Sub))
Else
lblStatus.Text = text
btnThread.Enabled = True
End If
End Sub
| 成员 | 说明 |
|---|---|
new Thread(…) |
创建线程;传入无参方法或 ParameterizedThreadStart |
IsBackground |
true 时进程退出不等待该线程(窗体应用常用) |
Start / Join |
启动 / 等待结束;勿在 UI 上长时间 Join |
InvokeRequired |
当前是否非控件创建线程 |
Invoke / BeginInvoke |
同步 / 异步封送到 UI;后台更新控件用其一 |
Thread.Sleep |
阻塞当前线程;勿在 UI 线程调用 |
同步调用放在何处
必须用同步 API 时:放到 Task.Run / 工作线程,或仅在非 UI 路径(控制台、服务)直接调用。UI 事件里优先异步。
C#
// UI 事件:把阻塞调用丢进线程池
private async void btnSyncApi_Click(object sender, EventArgs e)
{
lblStatus.Text = "请稍候…";
string msg = await Task.Run(delegate
{
// 假设 SomeLegacyApi.Call() 会阻塞数秒
Thread.Sleep(1000);
return "同步 API 已在后台跑完";
});
lblStatus.Text = msg;
}
VB.NET
' UI 事件:把阻塞调用丢进线程池
Private Async Sub btnSyncApi_Click(sender As Object, e As EventArgs) Handles btnSyncApi.Click
lblStatus.Text = "请稍候…"
Dim msg As String = Await Task.Run(Function()
Thread.Sleep(1000)
Return "同步 API 已在后台跑完"
End Function)
lblStatus.Text = msg
End Sub
注意事项
| 要点 | 说明 |
|---|---|
| UI 线程 | 耗时工作离开 UI;改控件回到 UI(await 续接或 Invoke) |
| 死锁 | UI 上对「会回 UI」的任务调用 .Result / .Wait() 易死锁;改用 await |
async void |
仅用于事件处理;业务方法返回 Task |
| 异常 | await 路径用 try/catch;裸 Thread 内异常需自行捕获并回 UI 提示 |
| 关闭窗体 | 后台仍跑时可能 Invoke 到已释放控件;关闭前取消任务或判断 IsDisposed |
| C# 语言版本 | LCode / .NET 4.8 按 C# 5.0:可用 async/await,勿用 $"…"、nameof、?. 等 |