IDbConnection.BeginTransaction в потоке графического интерфейса, используя async / await, вызывая взаимоблокировку с несколькими вызовами - PullRequest
0 голосов
/ 30 января 2019

У меня проблема, которую я полностью понимаю, но пытаюсь ее решить.

  1. У меня есть цикл обработки событий, который использует async / await.
  2. Мне также нужно использовать транзакции, которые в течение времени жизни будутесть некоторые другие продолжения.

С учетом сказанного рассмотрите этот код.

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 ing SingleByIdAsync второй вызов попытается набрать BeginTransaction, но он будет ждать там, пока первая транзакция не будет завершена.Ожидается, что за исключением того, что он заблокирует цикл основного события, предотвращая дальнейшие продолжения, оставляя первую транзакцию открытой.

Boom, deadlock.

Это будет решеноесли бы был 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()) является приемлемым решением?

1 Ответ

0 голосов
/ 30 января 2019

С учетом сказанного, видите ли вы какой-либо вред при открытии транзакции потока, который не открыл соединение с базой данных?

Здесь есть несколько соображений.Первый открывает транзакцию в другом потоке, чем тот, который открыл соединение БД.Другой способ заключается в том, что вы фиксируете и размещаете транзакцию в потоке, отличном от того, который ее открыл.

Ожидается 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();
    }
  }
}

Или, используя AsyncEx для лучшего синтаксиса :

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