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

发布于 2024-09-25 12:56:37 字数 80 浏览 0 评论 0原文

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

I have to make a sequence of method calls in C# such that, if one of them fails, the subsequent methods should not be called. In short, the set of calls should be made atomic. How do I achieve this in C#?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

┈┾☆殇 2024-10-02 12:56:37

我认为你将“原子”这个词与其他东西混淆了。原子性是指操作不能被中断,通常在多线程场景中完成以保护共享资源。

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

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

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
}

如果您需要回滚功能,则必须进入事务,但这是一个更大的主题。

I think you're confusing the word "atomic" with something else. Atomic is when an operation cannot be interrupted and is usually done in multi threaded scenarios to protect shared resources.

What you want is normal control flow logic and the solution depends on what your methods looks like.

One solution could be to have them return a boolean indicating whether or not it succeeded:

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

You could also use exceptions and wrap all your method calls inside a try-catch block. If one of them fails (throws an exception), your catch block will execute and nothing after that method call in the try-block will get a chance to run.

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

If you need rollback functionality, you have to get into transactions but that's a whole bigger topic.

猥琐帝 2024-10-02 12:56:37

TransactionScope 可能是您需要的,请参见此处

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

TransactionScope might be what you need see here

void RootMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          SomeMethod();
          SomeMethod2();
          SomeMethod3();
          scope.Complete();
     }
}
○闲身 2024-10-02 12:56:37

最好让它们在失败时抛出异常,并将您的调用序列写入 try/catch 块中。

如果由于某种原因无法做到这一点,请让它们在成功时返回 true 并使用 &&:(

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

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

Preferably, get them to throw an exception when they fail and write your call sequence in a try/catch block.

If you can't do that for some reason, get them to return true on success and use &&:

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

(That's not "atomic" in the true sense of the word, but I don't think you're asking for true atomicity.)

缺⑴份安定 2024-10-02 12:56:37

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

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

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

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

if one of them fails, the subsequent methods should not be called. In short, the set of calls should be made atomic.

That is not what atomic means. As other answers have explained you can achieve this simply by checking the result of each method call and stopping when you get a certain result.

Atomic means that either all of the methods are called or none of them are. So you guarantee that the entire block runs or doesn't run at all. This is not something you can achieve in C# code, or in most languages that I'm aware of.

What you would have to do is separate checking results from final processing and queue up the final processing instead.

So instead of:

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

Do something like:

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

This still isn't anywhere's close to being actually "atomic", but it does give you a very basic "all or nothing" effect.

新人笑 2024-10-02 12:56:37

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

If you are not catching exceptions, then if you throw an exception, all other methods called abort until a try block is found. So simply throw an exception where you need to have the atomic calls end (e.g. when it fails) and then catch it when you need to return to normal rutine.

春花秋月 2024-10-02 12:56:37

这是一个在出现问题时模拟带有补偿的移动操作的粗略示例。设备复制方法失败时抛出异常

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

Here is a rough example of emulating a move operation with compensation if things go wrong. With exceptions thrown from your device copy methods on failure

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

Or with error codes e.g. could be bool for failed or check an integer

if (DeviceCopy(source, dest))
{
    if (!DeviceDelete(source))
    {
        if (!DeviceDelete(dest))
        {
            throw new IOException("Oh noes");
        }
    }
}
韶华倾负 2024-10-02 12:56:37

您是否正在考虑多播代表?类似于:

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
}

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

Are you thinking of multicast delegates? Something like:

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
}

This won't work, because the order that the functions is called in is undefined. Plus, since your delegate functions can't return a value, there's no way to indicate that they failed, except by throwing an exception. And if you're going to throw an exception, you're better off using one of the earlier suggestions, as then your order of execution is defined (and your methods don't all have to have the same signature).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文