DoAsync
、ConnectFtpAsync
、ConnectDbAsync
都适用于控制台项目。ConnectFtpAsync
和 ConnectDbAsync
(除了 DoAsync
)会导致 WPF 项目冻结。
ButtonPressedAsync()
是最外层的调用,不使用ConfigureAwait(false)
,并且内部调用中使用的ConfigureAwait(false)
应该不重要。
从内部调用中删除
ConfigureAwait(false)
并没有解决问题。FluentFTP 和 Oracle 用于给定代码。
分别测试了3个例子。
ConnectFtpAsync
和ConnectDbAsync
冻结WPF项目?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();
}
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();
}
}