我有一个问题,我完全理解,但正在努力解决。
- 我有一个使用
async/await的事件循环。 - 我在所有数据库操作上使用
async/await,所有的延续都着陆于运行事件循环的同一个线程。 - 我还需要使用事务,它在其生命周期内会有一些其他的延续。
话虽如此,请考虑这段代码。
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
{
using (var transaction = connection.BeginTransaction())
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}
}
此方法的调用者来自主事件循环。
我首先创建一个事务。使用 SqlLite,这实际上是一个数据库级互斥量。这意味着当事务正在进行时,对 BeginTransaction 的其他调用将被阻止。
现在,假设此方法被快速连续调用多次。在 awaiting SingleByIdAsync 之后,第二个调用将尝试 BeginTransaction,但它将在那里等待直到第一个事务完成。这是预期的,除了它会阻塞主事件循环,防止任何进一步的继续发生,使第一个事务保持打开状态。
繁荣,僵局。
如果有 IDbConnection.BeginTransactionAsync,这将得到解决,但实际上没有。这样我就可以跳出事件循环,并在事务成功打开后继续。
因此,请考虑以下修复:
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
{
// Note that we are awaiting the opening of the transaction.
using (var transaction = await Task.Run(() => connection.BeginTransaction()))
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}
}
话虽如此,您认为打开未打开数据库连接的线程的事务有什么坏处吗?为什么没有 IDbConnection.BeginTransactionAsync? await Task.Run(() => connection.BeginTransaction()) 是一个可接受的解决方案吗?
请您参考如下方法:
With that said, do you see any harm in opening a transaction of a thread that didn't open the database connection?
这里有几个注意事项。第一个是在与打开数据库连接的线程不同的线程上打开事务。另一个是您在与打开它的线程不同的线程上提交和处理事务。
Is await Task.Run(() => connection.BeginTransaction()) an acceptable solution?
“这是个问题吗?”的问题只能由您的(客户)数据库提供商回答。
如果你想确保它不会成为问题,你可以包含你自己的互斥量:
private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
{
await _mutex.WaitAsync();
try
{
using (var transaction = await connection.BeginTransaction())
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}
finally
{
_mutex.Release();
}
}
}
或者,using AsyncEx for a nicer syntax :
private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
public async Task UpdateItem(int mediaItemId)
{
using (var connection = _dataService.OpenDbConnection())
using (_mutex.LockAsync())
using (var transaction = await connection.BeginTransaction())
{
var item = await connection.SingleByIdAsync<MediaItem>(mediaItemId);
item.Index++;
await connection.UpdateAsync(item);
transaction.Commit();
}
}
