Haga que TransactionScope funcione con async / await

114

Estoy intentando integrarme async/ awaiten nuestro bus de servicio. Implementé un SingleThreadSynchronizationContextbasado en este ejemplo http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx .

Y funciona bien, excepto por una cosa: TransactionScope. Espero cosas dentro del TransactionScopey se rompe el TransactionScope.

TransactionScopeno parece funcionar bien con async/ await, ciertamente porque almacena cosas en el hilo usando ThreadStaticAttribute. Obtengo esta excepción:

"TransactionScope anidado incorrectamente".

Intenté guardar TransactionScopedatos antes de poner en cola la tarea y restaurarla antes de ejecutarla, pero parece que no cambia nada. Y el TransactionScopecódigo es un desastre, por lo que es muy difícil entender qué está pasando allí.

¿Hay alguna forma de hacerlo funcionar? ¿Existe alguna alternativa a TransactionScope?

Yann
fuente
Aquí hay un código muy simple para reproducir un error de TransactionScope pastebin.com/Eh1dxG4a excepto que la excepción aquí es Transaction Aborted
Yann
¿Puedes usar una transacción SQL regular? ¿O está abarcando varios recursos?
Marc Gravell
Estoy abarcando múltiples recursos
Yann
Parece que necesitará pasar el alcance a su método asincrónico o darle una forma de recuperarlo de algún tipo de contexto común que se identifique con su unidad de trabajo.
Bertrand Le Roy
Necesitará un hilo independiente con el suyo propio SingleThreadSynchronizationContextpara cada nivel superior TransactionScope.
Stephen Cleary

Respuestas:

161

En .NET Framework 4.5.1, hay un conjunto de nuevos constructoresTransactionScope que toman un TransactionScopeAsyncFlowOptionparámetro.

Según MSDN, permite el flujo de transacciones a través de continuaciones de subprocesos.

Tengo entendido que está destinado a permitirle escribir código como este:

// 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
fuente
10

Un poco tarde para una respuesta, pero estaba teniendo el mismo problema con MVC4 y actualicé mi proyecto de 4.5 a 4.5.1 haciendo clic derecho en el proyecto para ir a propiedades. Seleccione la pestaña de la aplicación, cambie el marco de destino a 4.5.1 y use la transacción de la siguiente manera.

using (AccountServiceClient client = new AccountServiceClient())
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
}
Atul Chaudhary
fuente
2
¿En qué se diferencia esto de la respuesta aceptada?
Liam
6

Puede utilizar DependentTransaction creado por el método 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();
}

Gestión de la simultaneidad con DependentTransaction

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

maximpa
fuente
2
La tarea secundaria de ejemplo de Adam Prescott no se marcó como asincrónica. Si reemplaza "hacer cosas transaccionales" con algo como await Task.Delay(500)este patrón, también fallará TransactionScope nested incorrectlyporque el TransactionScope más externo (no mostrado en el ejemplo anterior) sale del alcance antes de que la tarea secundaria se complete correctamente. Reemplace awaitcon Task.Wait()y funciona, pero luego ha perdido los beneficios de async.
mdisibio
Ésta es una forma más difícil de resolver el problema. TransactionScope es ocultar toda esa plomería.
Eniola