DoAsync
, ConnectFtpAsync
, ConnectDbAsync
tudo funciona no projeto Console quando o número de tarefas excede o limite do semáforo.
No entanto ConnectFtpAsync
e ConnectDbAsync
exceto DoAsync
fazem com que o projeto WPF congele quando o número de tarefas excede o limite do semáforo.
ButtonPressedAsync()
qual é a chamada mais externa não usa o ConfigureAwait(false)
e ConfigureAwait(false)
usado na chamada interna não deve importar.
Remover o ConfigureAwait(false)
from inner calls não resolveu o problema.
Remover o semáforo ou não exceder o limite do semáforo resolveu o problema.
FluentFTP e Oracle são usados para o código fornecido.
3 exemplos são testados separadamente.
- Por que
ConnectFtpAsync
congelarConnectDbAsync
um projeto WPF? - Por que
DoAsync
o projeto WPF não congela?
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();
}
Semaphore
e outros eventos do kernel não são realmente compatíveisasync
porque bloqueiam completamente a execução.Então o que você está obtendo é um Deadlock Async clássico, porque o código está sendo executado no thread da IU e travando esperando pelo semáforo. Isso acontece principalmente em aplicativos GUI como WPF, em vez de aplicativos de console onde normalmente não há contexto de sincronização.
Isso pode ser evitado usando
ConfigureAwait(false)
, mas você precisa garantir que isso seja usado em toda a pilha, o que no caso de bibliotecas externas é difícil de garantir.A resposta real é nunca bloquear em código assíncrono . Você precisa de um evento wait que possa suspender a execução via
async
, comoSempahoreSlim
.Observe também que
Release
deve ser chamado em afinally
para garantir que ele sempre seja chamado, mesmo em caso de exceção.