信号量导致 WPF 上的死锁

问题描述 投票:0回答:1
当任务数量超过信号量限制时,

DoAsync
ConnectFtpAsync
ConnectDbAsync
都适用于控制台项目。
但是,当任务数量超过信号量限制时,
ConnectFtpAsync
ConnectDbAsync
(除了
DoAsync
)会导致 WPF 项目冻结。

ButtonPressedAsync()
是最外层的调用,不使用
ConfigureAwait(false)
,并且内部调用中使用的
ConfigureAwait(false)
应该不重要。

从内部调用中删除

ConfigureAwait(false)
并没有解决问题。
删除信号量或不超过信号量限制解决了问题。

FluentFTP 和 Oracle 用于给定代码。
分别测试了3个例子。

  1. 为什么
    ConnectFtpAsync
    ConnectDbAsync
    冻结WPF项目?
  2. 为什么
    DoAsync
    不冻结WPF项目?

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        await ButtonPressedAsync();
    }
    
    private static async Task ButtonPressedAsync()
    {
        try
        {
            var connectionLimit = 4;
            var smph = new Semaphore(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(Semaphore smph)
    {
        smph.WaitOne();
    
        await Task.Delay(500).ConfigureAwait(false);
    
        smph.Release();
    }
    
    private static async Task ConnectFtpAsync(Semaphore smph)
    {
        smph.WaitOne();
    
        var ftpConnection = new AsyncFtpClient(
            host: "ip",
            port: 21,
            user: "id",
            pass: "pswd");
    
        await ftpConnection.Connect().ConfigureAwait(false);
    
        smph.Release();
    }
    
    private static async Task ConnectDbAsync(Semaphore smph)
    {
        smph.WaitOne();
    
        var credential = "credential";
        using var dbConnection = new OracleConnection(credential);
        await dbConnection.OpenAsync().ConfigureAwait(false);
        await dbConnection.CloseAsync().ConfigureAwait(false);
    
        smph.Release();
    }

c# wpf async-await deadlock semaphore
1个回答
0
投票

Semaphore
和其他内核事件与
async
并不真正兼容,因为它们完全阻止执行。

所以你得到的是一个经典的异步死锁,因为代码正在 UI 线程上运行并锁定等待信号量。使用

ConfigureAwait(false)
可以避免这种情况,但您需要确保在堆栈中一直使用它,而在外部库的情况下很难确保这一点。

真正的答案是永远不要阻塞异步代码。您需要一个可以通过

async
暂停执行的等待事件,例如
SempahoreSlim

private static async Task ButtonPressedAsync()
{
    try
    {
        var connectionLimit = 4;
        using 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();
    try
    {
        await Task.Delay(500).ConfigureAwait(false);
        // do other stuff
    }
    finally
    {
        smph.Release();
    }
}

private static async Task ConnectFtpAsync(Semaphore smph)
{
    await smph.WaitAsync();
    try
    {
        // make sure to dispose your connection
        using var ftpConnection = new AsyncFtpClient(
            host: "ip",
            port: 21,
            user: "id",
            pass: "pswd");

        await ftpConnection.Connect().ConfigureAwait(false);
        // do stuff with FTP connection
    }
    finally
    {
        smph.Release();
    }
}

private static async Task ConnectDbAsync(Semaphore smph)
{
    await smph.WaitAsync();
    try
    {
        var credential = "credential";
        await using var dbConnection = new OracleConnection(credential);
        await dbConnection.OpenAsync().ConfigureAwait(false);
    }
    finally
    {
        smph.Release();
    }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.