C# - 如何使一系列方法调用原子?

时间:2010-09-29 11:42:02

标签: c# atomic atomicity

我必须在C#中进行一系列方法调用,这样,如果其中一个失败,则不应调用后续方法。简而言之,这组调用应该是原子的。我如何在C#中实现这一目标?

7 个答案:

答案 0 :(得分:7)

我认为你把“原子”这个词混淆了别的东西。 Atomic是指操作无法中断,通常在多线程场景下完成以保护共享资源。

你想要的是正常的控制流逻辑,解决方案取决于你的方法是什么样的。

一种解决方案可能是让它们返回一个布尔值,表明它是否成功:

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-block中调用该方法之后没有任何东西可以运行。

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

如果你需要回滚功能,你必须进入交易,但这是一个更大的主题。

答案 1 :(得分:4)

您可能需要使用TransactionScope查看here

void RootMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          SomeMethod();
          SomeMethod2();
          SomeMethod3();
          scope.Complete();
     }
}

答案 2 :(得分:1)

最好在失败时让他们抛出异常,并在try / catch区块中写下您的通话顺序。

如果因某些原因无法做到这一点,请让他们在成功时返回true并使用&&

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

(这不是真正意义上的“原子”,但我认为你并没有要求真正的原子性。)

答案 3 :(得分:1)

  

如果其中一个失败,则不应调用后续方法。简而言之,这组调用应该是原子的。

这不是原子的意思。正如其他答案所解释的那样,您可以通过检查每个方法调用的结果并在获得特定结果时停止来实现此目的。

Atomic意味着要么调用所有方法,要么都不调用。因此,您可以保证整个块运行或根本不运行。这不是您可以在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();
}

这仍然不是真正的“原子”,但它确实给你一个非常基本的“全有或全无”的效果。

答案 4 :(得分:0)

如果您没有捕获异常,那么如果您抛出异常,则所有其他方法都会被调用,直到找到try块。因此,只需抛出一个异常,你需要将原子调用结束(例如当它失败时),然后在你需要返回正常的rutine时捕获它。

答案 5 :(得分:0)

这是一个粗略的例子,如果出现问题,可以通过补偿来模拟移动操作。在失败时从设备复制方法抛出异常

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");
        }
    }
}

答案 6 :(得分:0)

您在考虑多播代理吗?类似的东西:

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
}

这不起作用,因为调用函数的顺序是未定义的。另外,由于您的委托函数无法返回值,因此除非通过抛出异常,否则无法指示它们是否失败。如果您要抛出异常,最好使用之前的建议之一,因为您的执行顺序已定义(并且您的方法并非都必须具有相同的签名)。