Зачисление ресурсов в транзакцию в качестве участников

Каждым участвующим в транзакции ресурсом управляет диспетчер ресурсов, действия которого координируются диспетчером транзакций. Координация осуществляется посредством уведомлений, которые передаются подписчикам, зачисленным в транзакцию с помощью диспетчера транзакций.

В этом разделе описывается, как зачислить в транзакцию один или несколько ресурсов, и рассматриваются различные типы зачисления. В разделе о фиксации транзакции в однофазном и многофазном разделе описывается, как обязательства по транзакциям могут быть согласованы между зачисленными ресурсами.

Зачисление ресурсов в транзакцию

Для участия в транзакции ресурс должен зачислиться в нее. Класс Transaction определяет набор методов, имена которых начинаются с enlist , которые предоставляют эту функцию. Различные методы enlist соответствуют различным типам зачисления, которые может иметь диспетчер ресурсов. В частности, методы EnlistVolatile используются для неустойчивых ресурсов, а метод EnlistDurable - для устойчивых ресурсов. Устойчивость (или наоборот, неустойчивость) диспетчера ресурсов определяет, поддерживает ли он восстановление после сбоя. Если диспетчер ресурсов поддерживает восстановление после сбоя, он сохраняет данные в устойчивом хранилище во время фазы 1 (подготовка); таким образом, если диспетчер ресурсов выходит из строя, он может повторно зачислиться в транзакцию после восстановления и выполнить соответствующие действия на основе уведомлений, полученных от диспетчера транзакций. В основном, диспетчеры неустойчивых ресурсов управляют неустойчивыми ресурсами, такими как структура данных, расположенная в оперативной памяти (например транзакционная хэш-таблица), а диспетчеры устойчивых ресурсов управляют устойчивыми ресурсами, которые имеют более устойчивое резервное хранилище (например, базой данных, резервным хранилищем которой является диск).

После выбора используемого метода (EnlistDurable или EnlistVolatile) в зависимости от поддержки ресурсом восстановления после сбоя необходимо зачислить ресурс для участия в двухфазной фиксации путем реализации интерфейса IEnlistmentNotification для диспетчера ресурсов. Дополнительные сведения о 2PC см. в разделе "Фиксация транзакции в однофазном и многофазном режиме".

Один участник может зачислиться в транзакцию с использованием более одного протокола, вызвав методы EnlistDurable и EnlistVolatile несколько раз.

Зачисление устойчивых ресурсов

Методы EnlistDurable используются для зачисления диспетчера ресурсов в транзакцию в качестве устойчивого ресурса. Предполагается, что в случае выхода из строя в ходе транзакции диспетчер устойчивых ресурсов может выполнить восстановление после повторного зачисления (с помощью метода Reenlist) во все транзакции, в которых он участвовал, но в которых он не выполнил операции фазы 2, и вызвать метод RecoveryComplete после завершения восстановления. Дополнительные сведения о восстановлении см. в разделе "Выполнение восстановления".

Все методы EnlistDurable принимают в качестве первого параметра объект Guid. Объект Guid используется диспетчером транзакций для связывания зачисленного устойчивого ресурса с конкретным диспетчером ресурсов. Поэтому крайне важно, чтобы диспетчер ресурсов согласованно использовал один и тот же объект Guid для идентификации себя среди различных диспетчеров ресурсов при перезапуске, иначе может не удастся выполнить восстановление.

Второй параметр метода EnlistDurable является ссылкой на объект, реализуемый диспетчером ресурсов для получения уведомлений транзакции. Используемый перегруженный метод сообщает диспетчеру транзакций, поддерживает ли диспетчер ресурсов оптимизацию однофазной фиксации. В большинстве случаев требуется реализовать интерфейс IEnlistmentNotification для принятия участия в двухфазной фиксации. Однако если требуется оптимизировать процесс фиксации, можно реализовать интерфейс ISinglePhaseNotification для однофазной фиксации. Дополнительные сведения о SPC см. в разделе "Фиксация транзакции в однофазном и многофазном режиме" и "Оптимизация" с помощью однофазной фиксации и уведомления с одним этапом.

Третий параметр - это перечисление EnlistmentOptions, значением которого может являться None или EnlistDuringPrepareRequired. Если задано значение EnlistDuringPrepareRequired, при получении уведомления о подготовке от диспетчера транзакций можно зачислить дополнительные диспетчеры ресурсов. Однако следует иметь в виду, что данный тип зачисления не подходит для оптимизации однофазной фиксации.

Зачисление неустойчивых ресурсов

Участникам, управляющим неустойчивыми ресурсами, такими как кэш, необходимо зачисляться с помощью методов EnlistVolatile. Такие объекты могут быть неспособны получить результат транзакции или восстановить состояние какой-либо транзакции, к которой они присоединяются после сбоя.

Как уже было отмечено, диспетчер ресурсов выполняет зачисление неустойчивого ресурса, если он управляет неустойчивым ресурсом, расположенным в оперативной памяти. Одно из преимуществ использования метода EnlistVolatile заключается в том, что он не приводит к передаче транзакции на следующий уровень иерархии, если эта передача не является необходимой. Дополнительные сведения о эскалации транзакций см. в разделе "Эскалация транзакций". Включение волатильности подразумевает как разницу в том, как зачисление обрабатывается диспетчером транзакций, а также ожидаемым от диспетчера ресурсов диспетчером транзакций. Это обусловлено тем, что диспетчер неустойчивых ресурсов не выполняет восстановление. Методы EnlistVolatile не принимают параметр Guid, поскольку диспетчер неустойчивых ресурсов не выполняет восстановление и не вызывает метод Reenlist, которому требуется объект Guid.

Как и в случае зачисления устойчивых ресурсов используемый для зачисления перегруженный метод указывает диспетчеру транзакций, поддерживает ли диспетчер ресурсов оптимизацию однофазной фиксации. Поскольку диспетчер неустойчивых ресурсов не может выполнять восстановление, никакие данные для восстановления не записываются для зачисленного неустойчивого ресурса во время фазы подготовки. Таким образом, при вызове метода RecoveryInformation возникает исключение InvalidOperationException.

В следующем примере показано, как с помощью метода EnlistVolatile зачислить объект в транзакцию в качестве участника.

static void Main(string[] args)
{
    try
    {
        using (TransactionScope scope = new TransactionScope())
        {

            //Create an enlistment object
            myEnlistmentClass myEnlistment = new myEnlistmentClass();

            //Enlist on the current transaction with the enlistment object
            Transaction.Current.EnlistVolatile(myEnlistment, EnlistmentOptions.None);

            //Perform transactional work here.

            //Call complete on the TransactionScope based on console input
                            ConsoleKeyInfo c;
            while(true)
                            {
                Console.Write("Complete the transaction scope? [Y|N] ");
                c = Console.ReadKey();
                Console.WriteLine();

                                    if ((c.KeyChar == 'Y') || (c.KeyChar == 'y'))
                {
                    scope.Complete();
                    break;
                }
                else if ((c.KeyChar == 'N') || (c.KeyChar == 'n'))
                {
                    break;
                }
            }
        }
    }
    catch (System.Transactions.TransactionException ex)
    {
        Console.WriteLine(ex);
    }
    catch
    {
        Console.WriteLine("Cannot complete transaction");
        throw;
    }
}

class myEnlistmentClass : IEnlistmentNotification
{
    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        Console.WriteLine("Prepare notification received");

        //Perform transactional work

        //If work finished correctly, reply prepared
        preparingEnlistment.Prepared();

        // otherwise, do a ForceRollback
        preparingEnlistment.ForceRollback();
    }

    public void Commit(Enlistment enlistment)
    {
        Console.WriteLine("Commit notification received");

        //Do any work necessary when commit notification is received

        //Declare done on the enlistment
        enlistment.Done();
    }

    public void Rollback(Enlistment enlistment)
    {
        Console.WriteLine("Rollback notification received");

        //Do any work necessary when rollback notification is received

        //Declare done on the enlistment
        enlistment.Done();
    }

    public void InDoubt(Enlistment enlistment)
    {
        Console.WriteLine("In doubt notification received");

        //Do any work necessary when in doubt notification is received

        //Declare done on the enlistment
        enlistment.Done();
    }
}
    Public Shared Sub Main()
        Try
            Using scope As TransactionScope = New TransactionScope()

                'Create an enlistment object
                Dim myEnlistmentClass As New EnlistmentClass

                'Enlist on the current transaction with the enlistment object
                Transaction.Current.EnlistVolatile(myEnlistmentClass, EnlistmentOptions.None)

                'Perform transactional work here.

                'Call complete on the TransactionScope based on console input
                Dim c As ConsoleKeyInfo
                While (True)
                    Console.Write("Complete the transaction scope? [Y|N] ")
                    c = Console.ReadKey()
                    Console.WriteLine()
                    If (c.KeyChar = "Y") Or (c.KeyChar = "y") Then
                        scope.Complete()
                        Exit While
                    ElseIf ((c.KeyChar = "N") Or (c.KeyChar = "n")) Then
                        Exit While
                    End If
                End While
            End Using
        Catch ex As TransactionException
            Console.WriteLine(ex)
        Catch
            Console.WriteLine("Cannot complete transaction")
            Throw
        End Try
    End Sub
End Class

Public Class EnlistmentClass
    Implements IEnlistmentNotification

    Public Sub Prepare(ByVal myPreparingEnlistment As PreparingEnlistment) Implements System.Transactions.IEnlistmentNotification.Prepare
        Console.WriteLine("Prepare notification received")

        'Perform transactional work

        'If work finished correctly, reply with prepared
        myPreparingEnlistment.Prepared()
    End Sub

    Public Sub Commit(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Commit
        Console.WriteLine("Commit notification received")

        'Do any work necessary when commit notification is received

        'Declare done on the enlistment
        myEnlistment.Done()
    End Sub

    Public Sub Rollback(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Rollback
        Console.WriteLine("Rollback notification received")

        'Do any work necessary when rollback notification is received

        'Declare done on the enlistment
        myEnlistment.Done()
    End Sub

    Public Sub InDoubt(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.InDoubt
        Console.WriteLine("In doubt notification received")

        'Do any work necessary when in doubt notification is received

        'Declare done on the enlistment
        myEnlistment.Done()
    End Sub
End Class

Оптимизация производительности

Класс Transaction также предоставляет метод EnlistPromotableSinglePhase для PSPE-зачисления. Это позволяет диспетчеру устойчивых ресурсов "владеть" и управлять транзакцией, которая затем при необходимости может быть повышена до транзакции MSDTC. Дополнительные сведения об этом см. в статье "Оптимизация с помощью однофазной фиксации и уведомления с одним этапом".

См. также