本页说明在 .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
多线程 ThreadTask.Run / Task.Factory.StartNew CPU 密集或必须阻塞的旧 API;结果回 UI 须 Invoke/BeginInvoke

在 LCode 中的准备

  1. 窗体项目默认已引用 SystemSystem.CoreTaskThread 一般无需额外浏览引用。
  2. C# 文件顶部加 using System.Threading;using System.Threading.Tasks;;VB 用 Imports 对应命名空间。
  3. 菜单 编译 → 编译解决方案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?.

相关阅读