Ottieni TransactionScope per lavorare con async / await


114

Sto cercando di integrare async/ awaitnel nostro bus di servizio. Ho implementato un SingleThreadSynchronizationContextbasato su questo esempio http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx .

E funziona bene, tranne per una cosa: TransactionScope. Aspetto roba dentro TransactionScopee rompa il file TransactionScope.

TransactionScopenon sembra giocare bene con async/ await, certamente perché memorizza le cose nel thread usando ThreadStaticAttribute. Ottengo questa eccezione:

"TransactionScope nidificato in modo errato.".

Ho provato a salvare i TransactionScopedati prima di accodare l'attività e ripristinarla prima di eseguirla, ma non sembra cambiare nulla. E il TransactionScopecodice è un casino, quindi è davvero difficile capire cosa sta succedendo lì.

C'è un modo per farlo funzionare? C'è qualche alternativa a TransactionScope?


Ecco un codice molto semplice per riprodurre un errore TransactionScope pastebin.com/Eh1dxG4a tranne per il fatto che l'eccezione qui è Transaction Aborted
Yann

Non puoi usare una normale transazione SQL? O stai distribuendo più risorse?
Marc Gravell

Sto utilizzando più risorse
Yann

Sembra che dovrai passare l'ambito al tuo metodo asincrono o dargli un modo per recuperarlo da una sorta di contesto comune identificato con la tua unità di lavoro.
Bertrand Le Roy

Avrai bisogno di un thread separato con il proprio SingleThreadSynchronizationContextper ogni livello superiore TransactionScope.
Stephen Cleary,

Risposte:


161

In .NET Framework 4.5.1, è disponibile una serie di nuovi costruttoriTransactionScope che accettano un TransactionScopeAsyncFlowOptionparametro.

Secondo MSDN, consente il flusso delle transazioni attraverso le continuazioni dei thread.

La mia comprensione è che ha lo scopo di consentirti di scrivere codice come questo:

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

Un po 'in ritardo per una risposta, ma stavo avendo lo stesso problema con MVC4 e ho aggiornato il mio progetto da 4.5 a 4.5.1 facendo clic con il pulsante destro del mouse su Project go to properties. Seleziona la scheda dell'applicazione cambia il framework di destinazione in 4.5.1 e usa la transazione come segue.

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

2
In che cosa differisce dalla risposta accettata?
Liam

6

È possibile utilizzare DependentTransaction creato dal metodo 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();
}

Gestione della concorrenza con DependentTransaction

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


2
L'attività figlio di esempio di Adam Prescott non è stata contrassegnata come asincrona. Se sostituisci "fai cose transazionali" con qualcosa come await Task.Delay(500)questo modello fallirà anche TransactionScope nested incorrectlyperché il TransactionScope più esterno (non mostrato nell'esempio sopra) esce dall'ambito prima che l'attività figlio venga completata correttamente. Sostituisci awaitcon Task.Wait()e funziona, ma poi hai perso i vantaggi di async.
mdisibio

Questo è un modo più difficile per risolvere il problema. TransactionScope consiste nel nascondere tutte quelle tubature.
Eniola
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.