You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Semaphore在WPF中引发死锁:FTP/DB异步任务致界面冻结

问题解答

1. 为何ConnectFtpAsync和ConnectDbAsync会导致WPF项目冻结?

核心原因是你用了传统的Semaphore(线程同步原语)而非异步友好的同步工具,且在UI线程上执行了阻塞式的smph.WaitOne()调用:

  • WPF按钮点击逻辑运行在UI线程,循环创建任务时,每个任务的smph.WaitOne()会同步在UI线程执行。
  • 当任务数超过信号量限制(比如5个任务抢4个名额),第5个任务会在UI线程上调用WaitOne()阻塞等待,直到有信号量名额释放。
  • 虽然后续异步操作加了ConfigureAwait(false),但WaitOne()本身是阻塞调用,会直接卡住UI线程。另外部分第三方库(如FluentFTP、Oracle驱动)的异步方法内部可能存在同步初始化逻辑,进一步延长了UI线程的阻塞时间,最终导致界面冻结。

2. 为何DoAsync不会导致WPF项目冻结?

DoAsync里的核心异步操作是await Task.Delay(500),这个方法会快速让出当前线程:

  • 前4个任务拿到信号量后,await Task.Delay(500)会立即返回未完成的Task,UI线程得以释放,继续处理界面消息。
  • 当Task.Delay完成后,smph.Release()会在后台线程执行,释放信号量名额,此时第5个任务的WaitOne()才能继续——整个过程UI线程阻塞时间极短,不会出现可感知的冻结。

修复方案:使用异步友好的SemaphoreSlim

把Semaphore替换为支持异步等待的SemaphoreSlim,用WaitAsync()替代阻塞式的WaitOne(),彻底避免UI线程被阻塞:

protected override async Task ExecuteAsync(CancellationToken ct)
{
    await ButtonPressedAsync();
}

private static async Task ButtonPressedAsync()
{
    try
    {
        var connectionLimit = 4;
        // 替换为异步友好的SemaphoreSlim
        var smph = new SemaphoreSlim(connectionLimit, connectionLimit);
        var tasks = new List<Task>();

        //for (var i = 0; i < connectionLimit + 1; ++i)
        //    tasks.Add(DoAsync(smph));

        for (var i = 0; i < connectionLimit + 1; ++i)
            tasks.Add(ConnectFtpAsync(smph));

        //for (var i = 0; i < connectionLimit + 1; ++i)
        //    tasks.Add(ConnectDbAsync(smph));

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        // 这里添加异常处理逻辑
    }
}

private static async Task DoAsync(SemaphoreSlim smph)
{
    await smph.WaitAsync().ConfigureAwait(false);

    await Task.Delay(500).ConfigureAwait(false);

    smph.Release();
}

private static async Task ConnectFtpAsync(SemaphoreSlim smph)
{
    await smph.WaitAsync().ConfigureAwait(false);

    var ftpConnection = new AsyncFtpClient(
        host: "ip",
        port: 21,
        user: "id",
        pass: "pswd");

    try
    {
        await ftpConnection.Connect().ConfigureAwait(false);
    }
    finally
    {
        await ftpConnection.Disconnect().ConfigureAwait(false);
        smph.Release();
    }
}

private static async Task ConnectDbAsync(SemaphoreSlim smph)
{
    await smph.WaitAsync().ConfigureAwait(false);

    var credential = "credential";
    using var dbConnection = new OracleConnection(credential);
    try
    {
        await dbConnection.OpenAsync().ConfigureAwait(false);
    }
    finally
    {
        await dbConnection.CloseAsync().ConfigureAwait(false);
        smph.Release();
    }
}

额外注意事项

  • 绝对不要在UI线程上执行阻塞式同步调用(如WaitOne()、Task.Wait()、Task.Result),这是WPF界面冻结的常见诱因。
  • 异步场景下优先使用SemaphoreSlim、Task.WhenAll这类异步友好工具,避免混用传统线程同步原语。

内容的提问来源于stack exchange,提问作者user

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:52:16