Получите TransactionScope для работы с async / await


114

Я пытаюсь интегрировать async/ awaitв нашу служебную шину. Я реализовал на SingleThreadSynchronizationContextоснове этого примера http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx .

И это работает отлично, за исключением одной вещи: TransactionScope. Я жду хрень TransactionScopeи ломаюсь TransactionScope.

TransactionScopeпохоже, не очень хорошо работает с async/ await, конечно, потому что он хранит вещи в потоке, используя ThreadStaticAttribute. Я получаю это исключение:

«TransactionScope неправильно вложен.».

Я пытался сохранить TransactionScopeданные перед постановкой задачи в очередь и восстановить их перед запуском, но, похоже, это ничего не меняет. А TransactionScopeкод - это беспорядок, поэтому очень сложно понять, что там происходит.

Есть ли способ заставить его работать? Есть ли альтернатива TransactionScope?


Вот очень простой код для воспроизведения ошибки TransactionScope pastebin.com/Eh1dxG4a, за исключением того, что здесь исключение - транзакция прервана
— Янн,

Можете ли вы использовать обычную транзакцию SQL? Или вы используете несколько ресурсов?
— Марк Грейвелл

Я охватываю несколько источников
— Янн

Похоже, вам нужно либо передать область видимости в свой асинхронный метод, либо дать ему возможность получить ее из какого-то общего контекста, который идентифицируется с вашей рабочей единицей.
— Бертран Ле Рой,

Вам понадобится отдельный поток со своим собственным SingleThreadSynchronizationContextдля каждого верхнего уровня TransactionScope.
— Стивен Клири

Ответы:


161

В .NET Framework 4.5.1 есть набор новых конструкторовTransactionScope , принимающих TransactionScopeAsyncFlowOptionпараметр.

Согласно MSDN, он обеспечивает поток транзакций через продолжения потоков.

Я понимаю, что он предназначен для того, чтобы вы могли писать такой код:

// transaction scope
using (var scope = new TransactionScope(... ,
  TransactionScopeAsyncFlowOption.Enabled))
{
  // connection
  using (var connection = new SqlConnection(_connectionString))
  {
    // open connection asynchronously
    await connection.OpenAsync();

    using (var command = connection.CreateCommand())
    {
      command.CommandText = ...;

      // run command asynchronously
      using (var dataReader = await command.ExecuteReaderAsync())
      {
        while (dataReader.Read())
        {
          ...
        }
      }
    }
  }
  scope.Complete();
}

10

Немного поздно для ответа, но у меня была такая же проблема с MVC4, и я обновил свой проект с 4.5 до 4.5.1, щелкнув правой кнопкой мыши проект, перейдите в свойства. Выберите вкладку приложения, измените целевую структуру на 4.5.1 и используйте транзакцию, как показано ниже.

using (AccountServiceClient client = new AccountServiceClient())
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
}

2
Чем это отличается от принятого ответа?
— Liam

6

Вы можете использовать DependentTransaction, созданный методом Transaction.DependentClone () :

static void Main(string[] args)
{
  // ...

  for (int i = 0; i < 10; i++)
  {

    var dtx = Transaction.Current.DependentClone(
        DependentCloneOption.BlockCommitUntilComplete);

    tasks[i] = TestStuff(dtx);
  }

  //...
}


static async Task TestStuff(DependentTransaction dtx)
{
    using (var ts = new TransactionScope(dtx))
    {
        // do transactional stuff

        ts.Complete();
    }
    dtx.Complete();
}

Управление параллелизмом с помощью DependentTransaction

http://adamprescott.net/2012/10/04/transactionscope-in-multi-threaded-applications/


2
Дочерняя задача Адама Прескотта не была помечена как асинхронная. Если вы замените «делать транзакции» чем-то вроде await Task.Delay(500)этого шаблона, также произойдет сбой, TransactionScope nested incorrectlyпотому что самый внешний TransactionScope (не показанный в приведенном выше примере) выходит из области видимости до того, как дочерняя задача завершится должным образом. Замените awaitна, Task.Wait()и он заработает, но тогда вы потеряете все преимущества async.
— mdisibio 02 апр.'14

Это более сложный способ решить проблему. TransactionScope скрывает всю эту сантехнику.
— Eniola
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.