Подтвердить что ты не робот

Каков наилучший способ добавить механизм повтора/отката для задач sync/async в С#?

Представьте себе приложение WebForms, где есть основной метод с именем CreateAll(). Я могу описать процесс задач метода шаг за шагом следующим образом:

1) Магазины в базу данных (обновить/создать элементы Db 3-4 раза)

2) Начинает новый поток

3) Result1 = Вызывает услугу мыла и с помощью порога тайм-аута проверяет состояние и после x минут. Он продолжает (статус теперь в порядке, и это не означает отказ)

4) Магазины в базу данных (обновить/создать элементы Db в 3-4 раза)

5) result2 = Вызывает услугу мыла (в режиме огня и забывания)

6) Обновляет файл конфигурации (который фактически берется из result1)

7) Используя обратные вызовы, он проверяет каждый х секунд на передней части состояние результата2, и пользовательский интерфейс показывает индикатор выполнения. Если процесс завершен (100%), это означает успех

Я рассматриваю, что все они являются задачами, которые могут быть сгруппированы по их типу. Как правило, несколько типов действий:

  • Тип1: транзакция DB
  • Тип2: служебная связь/транзакция
  • Тип3: операции ввода-вывода файла конфигурации

Я хочу добавить механизм отката/повтора в существующую реализацию и использовать целевую архитектуру и реорганизовать существующий унаследованный код.

Я обнаружил, что для этой цели может помочь что-то вроде шаблона проектирования Memento Design ИЛИ Command в С#. Я также нашел шаблон msdn Retry Pattern описание интересно. Я не знаю, и я хочу, чтобы кто-то привел меня к самому безопасному и лучшему решению...

Можете ли вы предложить мне лучший способ для этого случая сохранить существующую реализацию и поток, но обернуть его в общую и абстрактную реализацию retry/rollback/tasklist?

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

Я хочу несколько примеров, как разбить этот связанный код.


PseudoCode, который может быть полезен:

class something
{  
    static result CreateAll(object1 obj1, object2 obj2 ...)
    {
        //Save to database obj1
        //...
        //Update to database obj1 
        //
        //NEW THREAD
       //Start a new thread with obj1, obj2 ...CreateAll
       //...          
     } 

    void CreateAllAsync()
    {
        //Type1 Save to database obj1
        //...
        //Type1 Update to database obj2

        //Type2 Call Web Service to create obj1 on the service (not async)

        while (state != null && now < times)
        {
            if (status == "OK")
            break;      
            else
            //Wait for X seconds
        }

        //Check status continue or general failure
        //Type1 Update to database obj2 and obj1

        //Type2 Call Web Service to create obj2 on the service (fire and forget)

        //Type3 Update Configuration File
        //Type1 Update to database obj2 and obj1
        //..   

    return;
}

//Then the UI takes the responsibility to check the status of result2
4b9b3361

Ответ 1

Посмотрите на использование Polly для сценариев повтора, которые, похоже, хорошо согласуются с вашим псевдокодом. В конце этого ответа приведен образец из документации. Вы можете выполнять все виды повторных сценариев, повторять попытку и ждать и т.д. Например, вы могли бы повторно выполнить полную транзакцию несколько раз или, альтернативно, повторить набор идемпотентных действий несколько раз, а затем написать логику компенсации, если/когда повторная попытка политика, наконец, не удалась.

Мембранные шаблоны больше подходят для логики отмены, которые вы найдете в текстовом процессоре (Ctrl-Z и Ctrl-Y).

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

// Retry three times, calling an action on each retry 
// with the current exception and retry count
Policy
    .Handle<DivideByZeroException>()
    .Retry(3, (exception, retryCount) =>
    {
        // do something 
    });

Образец, основанный на вашем Псевдокоде, может выглядеть следующим образом:

static bool CreateAll(object1 obj1, object2 obj2)
{
     // Policy to retry 3 times, waiting 5 seconds between retries.
     var policy =
         Policy
              .Handle<SqlException>()
              .WaitAndRetry(3, count =>
              {
                 return TimeSpan.FromSeconds(5); 
              });

       policy.Execute(() => UpdateDatabase1(obj1));
       policy.Execute(() => UpdateDatabase2(obj2));
  }

Ответ 2

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

Изображение высокого уровня введите описание изображения здесь:

Обновление: намерение состоит в том, чтобы разделить беспокойство. Логика повторения ограничивается одним классом, который является оболочкой существующей команды. Вы можете сделать больше анализа и создать подходящие команды, объекты-вызовы и получатели и добавить функции отката.

public abstract class BaseCommand
{
    public abstract RxObservables Execute();
}

public class DBCommand : BaseCommand
{
    public override RxObservables Execute()
    {
        return new RxObservables();
    }
}

public class WebServiceCommand : BaseCommand
{
    public override RxObservables Execute()
    {
        return new RxObservables();
    }
}

public class ReTryCommand : BaseCommand // Decorator to existing db/web command
{
    private readonly BaseCommand _baseCommand;
    public RetryCommand(BaseCommand baseCommand)
    {
         _baseCommand = baseCommand
    }
    public override RxObservables Execute()
    {
        try
        {
            //retry using Polly or Custom
            return _baseCommand.Execute();
        }
        catch (Exception)
        {
            throw;
        }
    }
}

public class TaskDispatcher
{
    private readonly BaseCommand _baseCommand;
    public TaskDispatcher(BaseCommand baseCommand)
    {
        _baseCommand = baseCommand;
    }

    public RxObservables ExecuteTask()
    {
        return _baseCommand.Execute();
    }
}

public class Orchestrator
{
    public void Orchestrate()
    {
        var taskDispatcherForDb = new TaskDispatcher(new ReTryCommand(new DBCommand));
        var taskDispatcherForWeb = new TaskDispatcher(new ReTryCommand(new WebCommand));
        var dbResultStream = taskDispatcherForDb.ExecuteTask();
        var WebResultStream = taskDispatcherForDb.ExecuteTask();
    }
}

Ответ 3

Для меня это звучит как "Распределенные транзакции", так как у вас разные ресурсы (база данных, служебная связь, файловый ввод/вывод) и вы хотите сделать транзакцию, которая включает все из них.

В С# вы можете решить эту проблему с помощью Microsoft Distributed Transaction Coordinator. Для каждого ресурса вам нужен менеджер ресурсов. Насколько я знаю, для баз данных, таких как сервер sql и файл ввода/вывода, он уже доступен. Для других вы можете развивать свои собственные.

В качестве примера для выполнения этих транзакций вы можете использовать класс TransactionScope следующим образом:

using (TransactionScope ts = new TransactionScope())
{
    //all db code here

    // if an error occurs jump out of the using block and it will dispose and rollback

    ts.Complete();
}

(Пример из здесь)

Чтобы разработать собственный менеджер ресурсов, вам нужно реализовать IEnlistmentNotification, и это может быть довольно сложной задачей. Вот короткий пример.

Ответ 4

Некоторый код, который может помочь вам в достижении вашей цели.

public static class Retry
{
   public static void Do(
       Action action,
       TimeSpan retryInterval,
       int retryCount = 3)
   {
       Do<object>(() => 
       {
           action();
           return null;
       }, retryInterval, retryCount);
   }

   public static T Do<T>(
       Func<T> action, 
       TimeSpan retryInterval,
       int retryCount = 3)
   {
       var exceptions = new List<Exception>();

       for (int retry = 0; retry < retryCount; retry++)
       {
          try
          { 
              if (retry > 0)
                  Thread.Sleep(retryInterval);
              return action();
          }
          catch (Exception ex)
          { 
              exceptions.Add(ex);
          }
       }

       throw new AggregateException(exceptions);
   }
}

Вызовите и повторите попытку, как показано ниже:

int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);

Ссылка: http://gist.github.com/KennyBu/ac56371b1666a949daf8

Ответ 5

Ну... звучит как действительно, очень неприятная ситуация. Вы не можете открыть транзакцию, написать что-нибудь в базу данных и пойти гулять с собакой в ​​парк. Потому что у транзакций есть эта неприятная привычка блокировать ресурсы для всех. Это устраняет ваш лучший вариант: распределенные транзакции.

Я бы выполнил все операции и подготовил обратный script, когда я иду. Если операция успешна, я бы очистил script. В противном случае я его запустил. Но это открыто для потенциальных ловушек, и script должен быть готов к их обработке. Например - что, если в середине времени кто-то уже обновил записи, которые вы добавили; или рассчитывается совокупность на основе ваших значений?

Тем не менее: создание обратного script - это простое решение, без ракеты. Просто

List<Command> reverseScript;

а затем, если вам нужно откат:

using (TransactionScope tx= new TransactionScope()) {
    foreach(Command cmd in reverseScript) cmd.Execute();
    tx.Complete();
}