GUI线程上的IDbConnection.BeginTransaction使用异步/等待,导致多次调用死锁

时间:2019-01-30 17:33:48

标签: c# .net multithreading transactions async-await

我遇到了一个我完全理解的问题,但是正在努力解决。

  1. 我有一个使用async / await的事件循环。
  2. 我在所有数据库操作上都使用async / await,并且所有继续操作均落在运行事件循环的同一线程上。
  3. 我还需要使用在其生命周期内还会有其他延续的交易。​​

话虽如此,请考虑以下代码。

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.BeginTransactionAsyncawait Task.Run(() => connection.BeginTransaction())是否可以接受?

1 个答案:

答案 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();
  }
}