C # - как сделать последовательность вызовов методов атомарной? - PullRequest
2 голосов
/ 29 сентября 2010

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

Ответы [ 7 ]

7 голосов
/ 29 сентября 2010

Я думаю, вы путаете слово «атомный» с чем-то другим.Атомарность - это когда операция не может быть прервана и обычно выполняется в многопоточных сценариях для защиты общих ресурсов.

Требуется нормальная логика потока управления, и решение зависит от того, как ваши методы выглядят.1004 * Одним из решений может быть возвращение логического значения, указывающего, успешно ли оно выполнено:

bool success = false;

success = MethodA();
if (!success)
  return;
success = MethodB();
if (!success)
  return;

// or even like this as suggested in another answer
if (MethodA() &&
    MethodB() &&
    MethodC())
{
  Console.WriteLine("All succeeded");
}

Вы также можете использовать исключения и обернуть все вызовы методов внутри блока try-catch.Если один из них завершится неудачно (выдает исключение), ваш блок catch будет выполнен, и после вызова этого метода в блоке try ничего не получится.

try
{
  MethodA();
  MethodB();
  MethodC();
}
catch (MyMethodFailedException)
{
  // do something clever
}

Если вам нужна функциональность отката, выЯ должен вступать в сделки, но это большая тема.

4 голосов
/ 29 сентября 2010

TransactionScope может быть то, что вам нужно увидеть здесь

void RootMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          SomeMethod();
          SomeMethod2();
          SomeMethod3();
          scope.Complete();
     }
}
1 голос
/ 12 ноября 2015

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

Это не то, что означает атомарность.Как объясняли другие ответы, этого можно добиться, просто проверив результат каждого вызова метода и остановившись, когда вы получите определенный результат.

Атомный означает, что либо все методы вызваны, либо ни один из них не вызван.Таким образом, вы гарантируете, что весь блок работает или не работает вообще.Это не то, чего вы можете достичь с помощью кода C # или большинства известных мне языков.

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

Вместо:

bool Method1() {
   if (CheckCondition)
      return false;
   DoSomethingImportant();
   return true;
}

bool Method2() {
   if (CheckCondition)
      return false;
   DoSomethingElseImportant();
   return true;
}

...

var success1 = Method1();
if (!success1)
   return;

var success2 = Method2();
if (!success2)
   return; // oops we already did something important in method1

Сделайте что-то вроде:

bool Method1() {
   if (CheckCondition)
      return false;
   queue.Enqueue(DoSomethingImportant);
   return true;
}

bool Method2() {
   if (CheckCondition)
      return false;
   queue.Enqueue(DoSomethingElseImportant);
   return true;
}

...

var success1 = Method1();
if (!success1)
   return;

var success2 = Method2();
if (!success2)
   return; // no problem, nothing important has been invoked yet

// ok now we're completely successful, so now we can perform our important actions

while (queue.Any()) {
   var doSomethingImportant = queue.Dequeue();
   doSomethingImportant();
}

Это все еще далеко от того, чтобы быть фактически "атомарным", но оно дает вам очень простой эффект "все или ничего".

1 голос
/ 29 сентября 2010

Желательно, чтобы они выдавали исключение , когда они терпят неудачу, и записывали вашу последовательность вызовов в блок try / catch.

Если по какой-то причине вы не можете этого сделать, заставьте их вернуть true в случае успеха и используйте &&:

if (a() && b() && c())
{
    ....

(Это не «атомарно» в истинном смысле этого слова, но я не думаю, что вы просите об истинной атомности.)

0 голосов
/ 29 сентября 2010

Вы думаете о многоадресных делегатах?Что-то вроде:

delegate void SomeFunc(int x);

void foo() {
    SomeFunc funcSeq = new SomeFunc(func1);
    funcSeq += new SomeFunc(func2);
    funcSeq(100);   // Invokes func1 and func2
}

void func1(int foo) {
    // Use foo
}

void func2(int bar) {
    // Use bar
}

Это не сработает, потому что порядок вызова функций не определен.Кроме того, поскольку ваши функции-делегаты не могут возвращать значение, нет никакого способа указать, что они потерпели неудачу, кроме как с помощью исключения.И если вы собираетесь сгенерировать исключение, вам лучше использовать одно из более ранних предложений, поскольку тогда ваш порядок выполнения определен (и ваши методы не обязательно должны иметь одинаковую подпись).

0 голосов
/ 29 сентября 2010

Вот грубый пример эмуляции операции перемещения с компенсацией, если что-то пойдет не так. С исключениями, которые вызываются с вашего устройства, методы копирования при ошибке

string source = @"C:\file.txt", dest = @"D:\file.txt";

bool fileCopied = false;
try
{
    DeviceCopy(source, dest);
    fileCopied = true;
    DeviceDelete(source);
}
catch
{
    if (fileCopied)
    {
        DeviceDelete(dest);
    }
    throw;
}

Или с кодами ошибок, например может быть bool для сбоя или проверить целое число

if (DeviceCopy(source, dest))
{
    if (!DeviceDelete(source))
    {
        if (!DeviceDelete(dest))
        {
            throw new IOException("Oh noes");
        }
    }
}
0 голосов
/ 29 сентября 2010

Если вы не перехватываете исключения, то, если вы генерируете исключение, все остальные методы вызывают abort до тех пор, пока не будет найден блок try. Поэтому просто сгенерируйте исключение, когда вам нужно завершить атомарные вызовы (например, при сбое), а затем перехватите его, когда вам нужно вернуться к нормальной рутине.

...