Lassen Sie TransactionScope mit async / await arbeiten

114

Ich versuche async/ awaitin unseren Servicebus zu integrieren . Ich habe ein SingleThreadSynchronizationContextauf diesem Beispiel basierendes http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx implementiert .

Und es funktioniert gut, bis auf eine Sache : TransactionScope. Ich warte auf Sachen in der TransactionScopeund es bricht die TransactionScope.

TransactionScopescheint nicht gut mit dem async/ zu spielen await, sicherlich weil es Dinge im Thread mit speichert ThreadStaticAttribute. Ich bekomme diese Ausnahme:

"TransactionScope ist falsch verschachtelt."

Ich habe versucht, TransactionScopeDaten vor dem Einreihen in die Warteschlange zu speichern und vor dem Ausführen wiederherzustellen, aber es scheint nichts zu ändern. Und TransactionScopeCode ist ein Chaos, daher ist es wirklich schwer zu verstehen, was dort vor sich geht.

Gibt es eine Möglichkeit, es zum Laufen zu bringen? Gibt es eine Alternative zu TransactionScope?

Yann
quelle
Hier ist ein sehr einfacher Code zum Reproduzieren eines TransactionScope-Fehlers pastebin.com/Eh1dxG4a , mit der Ausnahme, dass die Ausnahme hier Transaktion abgebrochen ist
Yann
Können Sie nicht einfach eine reguläre SQL-Transaktion verwenden? Oder umfassen Sie mehrere Ressourcen?
Marc Gravell
Ich überspanne mehrere Ressourcen
Yann
Anscheinend müssen Sie den Bereich entweder an Ihre asynchrone Methode übergeben oder ihm eine Möglichkeit geben, ihn aus einem gemeinsamen Kontext abzurufen, der mit Ihrer Arbeitseinheit identifiziert wird.
Bertrand Le Roy
Sie benötigen SingleThreadSynchronizationContextfür jede oberste Ebene einen eigenen Thread TransactionScope.
Stephen Cleary

Antworten:

161

In .NET Framework 4.5.1 gibt es eine Reihe neuer Konstruktoren fürTransactionScope diesen TransactionScopeAsyncFlowOptionParameter.

Laut MSDN ermöglicht es den Transaktionsfluss über Thread-Fortsetzungen.

Mein Verständnis ist, dass es Ihnen ermöglichen soll, Code wie folgt zu schreiben:

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

Etwas spät für eine Antwort, aber ich hatte das gleiche Problem mit MVC4 und habe mein Projekt von 4.5 auf 4.5.1 aktualisiert, indem ich mit der rechten Maustaste auf Projekt geklickt habe. Gehe zu Eigenschaften. Wählen Sie die Registerkarte Anwendung, ändern Sie das Zielframework auf 4.5.1 und verwenden Sie die Transaktion wie folgt.

using (AccountServiceClient client = new AccountServiceClient())
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
}
Atul Chaudhary
quelle
2
Wie unterscheidet sich dies von der akzeptierten Antwort?
Liam
6

Sie können DependentTransaction verwenden, das mit der Transaction.DependentClone () -Methode erstellt wurde:

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

Verwalten der Parallelität mit DependentTransaction

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

maximpa
quelle
2
Die Beispielaufgabe von Adam Prescott wurde nicht als asynchron markiert. Wenn Sie "Transaktionsvorgänge ausführen" durch etwas wie await Task.Delay(500)dieses Muster ersetzen, schlägt dies ebenfalls fehl, TransactionScope nested incorrectlyda das äußerste TransactionScope (im obigen Beispiel nicht gezeigt) den Gültigkeitsbereich verlässt, bevor die untergeordnete Aufgabe ordnungsgemäß abgeschlossen wird. Ersetzen awaitdurch Task.Wait()und es funktioniert, aber dann haben Sie die Vorteile von verloren async.
mdisibio
Dies ist ein schwierigerer Weg, um das Problem zu lösen. TransactionScope soll all diese Installationen verbergen.
Eniola