Получите 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();
}
ZunTzu
источник
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/

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