Могут ли взаимодействия хранилища экземпляров WorkflowApplication участвовать в внешней транзакции? - PullRequest
1 голос
/ 28 июля 2011

У меня есть приложение, которое выполняет рабочие процессы, используя WorkflowApplication, а также делает другие вещи.Я хотел бы, чтобы и эти другие вещи, и взаимодействия WorkflowApplication с хранилищем экземпляров до вызова BeginRun () были ограничены транзакцией, поэтому я все обернул в одну.Однако введение этой транзакции приводит к зависанию таких вызовов, как WorkflowApplication.Persist ().Следующий пример представляет собой модифицированную версию базового образца персистентности экземпляра (описано здесь , загрузка с здесь ).Я изменил оригинал, определив область видимости всего сделанного в транзакции;это очень похоже на то, что делает мое приложение.

using System;
using System.Activities;
using System.Activities.DurableInstancing;
using System.Activities.Statements;
using System.Runtime.DurableInstancing;
using System.Threading;

namespace Microsoft.Samples.Activities
{
  using System.Transactions;

  class Program
  {
    static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
    static Activity activity = CreateWorkflow();
    static Guid id;

    const string readLineBookmark = "ReadLine1";

    static void Main()
    {
      StartAndUnloadInstance();
      LoadAndCompleteInstance();

      Console.WriteLine("Press [Enter] to exit.");
      Console.ReadLine();
    }

    static void StartAndUnloadInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (e) =>
          { handle.Free(); };

        application.PersistableIdle = (e) =>
            {
              return PersistableIdleAction.Unload;
            };

        application.Unloaded = (e) =>
            {
              instanceUnloaded.Set();
            };

        application.Persist();
        id = application.Id;
        application.Run();
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static void LoadAndCompleteInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        string input = Console.ReadLine();

        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (workflowApplicationCompletedEventArgs) =>
          {
            handle.Free();
            Console.WriteLine(
              "\nWorkflowApplication has Completed in the {0} state.",
              workflowApplicationCompletedEventArgs.CompletionState);
          };

        application.Unloaded = (workflowApplicationEventArgs) =>
          {
            Console.WriteLine("WorkflowApplication has Unloaded\n");
            instanceUnloaded.Set();
          };

        application.Load(id);

        //this resumes the bookmark setup by readline
        application.ResumeBookmark(readLineBookmark, input);
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static Sequence CreateWorkflow()
    {
      Variable<string> response = new Variable<string>();

      return new Sequence()
      {
        Variables = { response },
        Activities = { 
                        new WriteLine(){
                            Text = new InArgument<string>("What is your name?")},
                        new ReadLine(){ 
                            BookmarkName = readLineBookmark, 
                            Result = new OutArgument<string>(response)},
                        new WriteLine(){
                            Text = new InArgument<string>((context) => "Hello " + response.Get(context))}}
      };
    }

    private static InstanceStore SetupInstanceStore(out InstanceHandle handle)
    {
      SqlWorkflowInstanceStore instanceStore =
          new SqlWorkflowInstanceStore(@"Data Source=.;Initial Catalog=SampleInstanceStore;Integrated Security=True;Asynchronous Processing=True");

      handle = instanceStore.CreateInstanceHandle();
      InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));

      instanceStore.DefaultInstanceOwner = view.InstanceOwner;

      return instanceStore;
    }
  }
}

using System;
using System.Activities;
using System.Activities.DurableInstancing;
using System.Activities.Statements;
using System.Runtime.DurableInstancing;
using System.Threading;

namespace Microsoft.Samples.Activities
{
  using System.Transactions;

  class Program
  {
    static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
    static Activity activity = CreateWorkflow();
    static Guid id;

    const string readLineBookmark = "ReadLine1";

    static void Main()
    {
      StartAndUnloadInstance();
      LoadAndCompleteInstance();

      Console.WriteLine("Press [Enter] to exit.");
      Console.ReadLine();
    }

    static void StartAndUnloadInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (e) =>
          { handle.Free(); };

        application.PersistableIdle = (e) =>
            {
              return PersistableIdleAction.Unload;
            };

        application.Unloaded = (e) =>
            {
              instanceUnloaded.Set();
            };

        application.Persist();
        id = application.Id;
        application.Run();
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static void LoadAndCompleteInstance()
    {
      using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
      {
        string input = Console.ReadLine();

        WorkflowApplication application = new WorkflowApplication(activity);
        InstanceHandle handle;
        application.InstanceStore = SetupInstanceStore(out handle);

        application.Completed = (workflowApplicationCompletedEventArgs) =>
          {
            handle.Free();
            Console.WriteLine(
              "\nWorkflowApplication has Completed in the {0} state.",
              workflowApplicationCompletedEventArgs.CompletionState);
          };

        application.Unloaded = (workflowApplicationEventArgs) =>
          {
            Console.WriteLine("WorkflowApplication has Unloaded\n");
            instanceUnloaded.Set();
          };

        application.Load(id);

        //this resumes the bookmark setup by readline
        application.ResumeBookmark(readLineBookmark, input);
        ts.Complete();
      }

      instanceUnloaded.WaitOne();
    }

    static Sequence CreateWorkflow()
    {
      Variable<string> response = new Variable<string>();

      return new Sequence()
      {
        Variables = { response },
        Activities = { 
                        new WriteLine(){
                            Text = new InArgument<string>("What is your name?")},
                        new ReadLine(){ 
                            BookmarkName = readLineBookmark, 
                            Result = new OutArgument<string>(response)},
                        new WriteLine(){
                            Text = new InArgument<string>((context) => "Hello " + response.Get(context))}}
      };
    }

    private static InstanceStore SetupInstanceStore(out InstanceHandle handle)
    {
      SqlWorkflowInstanceStore instanceStore =
          new SqlWorkflowInstanceStore(@"Data Source=.;Initial Catalog=SampleInstanceStore;Integrated Security=True;Asynchronous Processing=True");

      handle = instanceStore.CreateInstanceHandle();
      InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));

      instanceStore.DefaultInstanceOwner = view.InstanceOwner;

      return instanceStore;
    }
  }
}

При запуске приложение зависает при вызове Persist () в StartAndUnloadInstance ().Зависание заканчивается примерно через пять минут (где-то есть тайм-аут).Пока вы зависли, вы можете увидеть причину, посмотрев на Activity Monitor в SQL Server.Вызов для создания владельца рабочего процесса в SetupInstanceStore () получает эксклюзивную блокировку для строки в LockOwnersTable.Вызов Persist () пытается получить такую ​​же блокировку.К сожалению.

В любом случае, более широкий вопрос, как указано в заголовке: может ли экземпляр хранилища взаимодействий, которые я делаю до вызова BeginRun (), работать в контексте внешней транзакции или нет?Если так, что я делаю не так?Если нет, какие варианты у меня есть?До сих пор я был в состоянии устранить зависание / тупик, окружив каждый случай, когда WorkflowApplication обращается к хранилищу экземпляров с помощью:

using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
{
  // Method call that interacts with the instance store.
  ts.Complete();
}

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

РЕДАКТИРОВАТЬ : Для более подробного описания я хочу, чтобы рабочий процесс сохранялся до выполнения в той же транзакции, что и другие операции с базой данных.Я хочу это:

  • Разрешить повторный запуск рабочего процесса, если во время выполнения рабочего процесса умирает хост-процесс.
  • Убедитесь, что откат персистентности и рабочий процесс отмененыникогда не запускается, если происходит сбой другой активности базы данных.

Сам рабочий процесс не нужно запускать в транзакции, только взаимодействия хранилища экземпляров, которые происходят до вызова BeginRun ().

Ответы [ 3 ]

2 голосов
/ 30 июля 2011

Хорошо, так что ответ на мой вопрос, похоже, нет. Я не могу найти способ устранить зависание при наличии внешней транзакции. Что я сделал, так это окружил каждое взаимодействие хранилища экземпляров (создать владельца рабочего процесса, сохранить) с

using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
{
  // Method call that interacts with the instance store.
  ts.Complete();
}

Это позволяет этой структуре запускать рабочие процессы:

  • Запустить единицу работы верхнего уровня (сеанс NHibernate).
    • Делай вещи.
    • Получить дескриптор хранилища экземпляров, выполнить CreateWorkflowOwnerCommand, чтобы получить блокировку.
    • Создание приложения рабочего процесса.
    • Сохранить рабочий процесс.
    • Звоните BeginRun.
  • Commit.

Недостаток? Если после Persist выдается исключение, отката нет; рабочий процесс будет запущен в какой-то момент. У меня есть некоторая инфраструктура, чтобы минимизировать проблему, но долгосрочный ответ - найти способ использовать AppFabric (на данный момент это невозможно из-за требуемых масштабов изменений, но будет рассмотрено в следующей версии).

0 голосов
/ 29 июля 2011

WorkflowApplication по умолчанию запускает рабочий процесс в потоке, отличном от потока хоста.Транзакция Ambient доступна только по коду, выполняемому в потоке хоста.Вы не можете разделить транзакцию Ambient между потоками.

Вы можете заставить WorkflowApplication использовать вызывающий поток, установив SynchronizationContext.Но лучшее решение - позволить каждому экземпляру рабочего процесса управлять своими собственными транзакциями.Если существует область транзакции, постоянство будет происходить в рамках транзакции.Рабочий процесс всегда сохраняется на границах транзакции.

0 голосов
/ 28 июля 2011

рабочий процесс может выполняться внутри транзакции, но для этого с помощью WorkflowApplication требуется больше работы. Для одного рабочий процесс фактически выполняется в фоновом потоке, а не в том, где вы создали TransactionScope. Если вы используете WorkflowInvoker, он будет выполняться как часть той же транзакции в том же потоке.

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

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

Но при длительных рабочих процессах гораздо лучше использовать CompensableActivity и компенсируемые транзакции.

...