我遇到了一个我完全理解的问题,但是正在努力解决。
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
的其他调用将被阻止。
现在,考虑快速连续地多次调用此方法。在await
设置为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())
是否可以接受?
答案 0 :(得分:3)
话虽如此,打开未打开数据库连接的线程的事务是否有任何危害?
这里有两个注意事项。第一个是在与打开db连接的线程不同的线程上打开事务。另一个是您要在与打开事务不同的线程上提交和处置事务。
正在等待Task.Run(()=> connection.BeginTransaction())可接受的解决方案吗?
“这有问题吗?”的问题只能由您的(客户端)数据库提供者回答。
如果要确保这不会成为问题,则可以包括自己的互斥锁:
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();
}
}