Verwalten von Parallelität mit DependentTransaction
Das Transaction-Objekt wird mit der DependentClone-Methode erstellt. Sein einziger Zweck besteht darin, sicherzustellen, dass die Transaktion keinen Commit durchführen kann, während andere Codeteile (beispielsweise ein Arbeitsthread) noch Aktionen für die Transaktion ausführen. Wenn die Aktionen innerhalb der geklonten Transaktion abgeschlossen und für den Commit bereit sind, kann es den Ersteller der Transaktion mithilfe der Complete-Methode informieren. Auf diese Weise können Sie Konsistenz und Richtigkeit der Daten bewahren.
Die DependentTransaction-Klasse kann ebenfalls verwendet werden, um Parallelität zwischen asynchronen Aufgaben zu verwalten. Bei diesem Szenario kann das übergeordnete Element weiter Code ausführen, während der abhängige Klon seine eigenen Aufgaben ausführt. Anders ausgedrückt wird die Ausführung des übergeordneten Elements nicht blockiert, solange die Ausführung des abhängigen Elements nicht abgeschlossen ist.
Erstellen eines abhängigen Klons
Um eine abhängige Transaktion zu erstellen, rufen Sie die DependentClone-Methode auf, und übergeben Sie die DependentCloneOption-Enumeration als Parameter. Dieser Parameter definiert das Verhalten der Transaktion, wenn Commit
für die übergeordnete Transaktion aufgerufen wird, bevor der abhängige Klon anzeigt, dass er für den Transaktionscommit bereit ist (durch Aufrufen der Complete-Methode). Die folgenden Werte sind für diesen Parameter gültig:
BlockCommitUntilComplete erstellt eine abhängige Transaktion, die den Commitprozess der übergeordneten Transaktion blockiert, bis für die übergeordnete Transaktion ein Timeout besteht, oder bis Complete für alle abhängigen Transaktionen aufgerufen wird, was deren Abschluss angibt. Das ist nützlich, wenn der Client den Commit der übergeordneten Transaktion erst zulassen will, wenn die abhängigen Transaktionen abgeschlossen sind. Wenn die übergeordnete Transaktion ihre Aufgaben früher abschließt als die abhängige Transaktion und Commit für die Transaktion aufruft, wird der Commitprozess in einem Zustand blockiert, in dem weitere Aufgaben für die Transaktion ausgeführt und neue Eintragungen vorgenommen werden können, bis alle abhängigen Transaktionen Complete aufrufen. Sobald alle ihre Aufgaben abgeschlossen und Complete aufgerufen haben, beginnt der Commitprozess für die Transaktion.
RollbackIfNotComplete hingegen erstellt eine abhängige Transaktion, die automatisch abgebrochen wird, wenn Commit für die übergeordnete Transaktion aufgerufen wird, bevor Complete aufgerufen wird. In diesem Fall bleiben alle für die abhängige Transaktion ausgeführten Aktionen innerhalb einer Transaktionslebensdauer intakt. Es ist nicht möglich, einen Commit nur für einen Teil davon auszuführen.
Die Complete-Methode darf nur einmal aufgerufen werden, wenn die Anwendung ihre Aufgaben für die abhängige Transaktion beendet hat; anderenfalls wird eine InvalidOperationException-Ausnahme ausgelöst. Führen Sie nach diesem Aufruf keine weiteren Aktionen für die Transaktion aus. Andernfalls wird eine Ausnahme ausgelöst.
Das folgende Codebeispiel zeigt, wie eine abhängige Transaktion mit dem Ziel erstellt wird, zwei parallele Aufgaben durch Klonen einer abhängigen Transaktion und Übergabe der Transaktion an einen Arbeitsthread zu verwalten.
public class WorkerThread
{
public void DoWork(DependentTransaction dependentTransaction)
{
Thread thread = new Thread(ThreadMethod);
thread.Start(dependentTransaction);
}
public void ThreadMethod(object transaction)
{
DependentTransaction dependentTransaction = transaction as DependentTransaction;
Debug.Assert(dependentTransaction != null);
try
{
using(TransactionScope ts = new TransactionScope(dependentTransaction))
{
/* Perform transactional work here */
ts.Complete();
}
}
finally
{
dependentTransaction.Complete();
dependentTransaction.Dispose();
}
}
//Client code
using(TransactionScope scope = new TransactionScope())
{
Transaction currentTransaction = Transaction.Current;
DependentTransaction dependentTransaction;
dependentTransaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
WorkerThread workerThread = new WorkerThread();
workerThread.DoWork(dependentTransaction);
/* Do some transactional work here, then: */
scope.Complete();
}
Der Clientcode erstellt einen Transaktionsbereich, der auch die Ambient-Transaktion festlegt. Sie sollten die Ambient-Transaktion nicht an den Arbeitsthread übergeben. Stattdessen sollten Sie die aktuelle (Ambient-)Transaktion klonen, indem Sie die DependentClone-Methode für die aktuelle Transaktion aufrufen und die abhängige an den Arbeitsthread übergeben.
Die ThreadMethod
-Methode wird für den neuen Thread ausgeführt. Der Client startet einen neuen Thread und übergibt die abhängige Transaktion als ThreadMethod
-Parameter.
Da die abhängige Transaktion mit BlockCommitUntilComplete erstellt wird, haben Sie die Gewissheit, dass der Transaktionscommit nicht ausgeführt werden kann, bevor alle Transaktionsaufgaben für den zweiten Thread abgeschlossen sind und Complete für die abhängige Transaktion aufgerufen wurde. Das bedeutet: Wenn der Clientbereich endet (wenn der Client versucht, am Ende der -Anweisung über das using
Transaktionsobjekt zu verfügen), bevor der neue Thread Complete für die abhängige Transaktion aufruft, blockiert der Clientcode, bis Complete für die abhängige Transaktion aufgerufen wird. Dann kann die Transaktion durch Commit oder Abbruch beendet werden.
Parallelitätsprobleme
Es gibt noch einige weitere Parallelitätsaspekte, die Sie beachten müssen, wenn Sie die DependentTransaction-Klasse verwenden:
Wenn der Arbeitsthread einen Rollback für die Transaktion ausführt, die übergeordnete Transaktion jedoch versucht, einen Commit dafür auszuführen, wird eine TransactionAbortedException-Ausnahme ausgelöst.
Sie sollten einen neuen abhängigen Klon für jeden Arbeitsthread in der Transaktion erstellen. Übergeben Sie nicht denselben abhängigen Klon an mehrere Threads, da nur einer davon Complete aufrufen kann.
Wenn der Arbeitsthread einen neuen Arbeitsthread erzeugt, müssen Sie einen abhängigen Klon des abhängigen Klons erstellen und an den neuen Thread übergeben.