繁体   English   中英

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

[英]C# - how to make a sequence of method calls atomic?

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

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

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

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

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
}

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

您可以在此处看到TransactionScope

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

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

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

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

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

最好让他们在失败时抛出异常并在try / catch块中编写你的调用序列。

如果由于某种原因你不能这样做,让他们在成功时返回true并使用&&

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

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

如果你没有捕获异常,那么如果你抛出异常,所有其他方法都会被称为abort,直到找到try块。 所以简单地抛出一个异常,你需要让原子调用结束(例如当它失败时),然后当你需要返回正常的rutine时捕获它。

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

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

你在想多播代表吗? 就像是:

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
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM