【问题标题】:How can I structure a try-catch-finally block to handle errors inside finally?如何构造一个 try-catch-finally 块来处理 finally 内部的错误?
【发布时间】:2017-09-14 09:55:55
【问题描述】:

我在调用第三方 C++ dll 时遇到问题,我使用 DllImport 将其封装在一个类中以访问其功能。

dll 要求在使用之前打开一个会话,它返回一个整数句柄,用于在执行操作时引用该会话。完成后,必须使用相同的句柄关闭会话。所以我做了这样的事情:

public void DoWork(string input)
{
    int apiHandle = DllWrapper.StartSession();

    try
    {
        // do work using the apiHandle
    }
    catch(ApplicationException ex)
    {
        // log the error
    }
    finally
    {
        DllWrapper.CloseSession(apiHandle);
    }
}

我遇到的问题是 CloseSession() 有时会导致有问题的 Dll 在运行线程时抛出错误:

System.AggregateException:发生一个或多个错误。 ---> System.AccessViolationException:试图读或写保护 记忆。这通常表明其他内存已损坏。

我不确定我能做些什么来阻止这个错误,因为它似乎是由以线程方式使用 Dll 引起的——它应该是线程安全的。但是由于我的 CloseSession() 函数除了调用 Dll 的 close 函数之外什么都不做,所以我没有太多的回旋余地来“修复”任何东西。

然而,最终结果是会话没有正确关闭。因此,当该进程再次尝试时,它应该这样做,它会遇到一个打开的会话并不断抛出新的错误。该会话绝对必须关闭。

我不知道如何设计一个更强大的错误处理语句来确保会话始终关闭?

【问题讨论】:

  • 我想问题是,错误应该如何“处理”?如果关闭会话从 3rd 方代码中引发错误,并且如果不关闭会话就无法继续,那么下一步怎么办?我的意思是,您可以在该finally 中添加另一个try/catch,但是您将如何实际响应该错误?如果第 3 方工具不起作用,该怎么办?
  • 如果这是由线程引起的,那么也许您可以通过在 StartSession() 和 CloseSession() 方法调用周围使用 lock(){} 语句来解决此问题。
  • @David 第三方工具失败是间歇性的——当你第二次尝试时,它通常可以工作。我的实际代码目前是一团糟的递归和控制流,试图确保这种情况总是发生。我不愿意添加更多,但如果必须,我必须:(
  • @MattThrower 你有没有实际测试过一次只通过一个线程使用这个不是真正线程安全的第 3 方库?如果你这样做,问题会消失吗?
  • 这是 32 位还是 64 位 DLL?句柄通常是指针大小的东西。您可能应该使用 IntPtr 而不是 int 作为句柄类型。

标签: c# multithreading dll error-handling unmanaged


【解决方案1】:

我会更改包装器以包括对外部资源的处理并同时包装句柄。 IE。您可以通过包装对象来表示会话,而不是通过句柄来表示会话。

此外,在lock-statements 中包装对DLL 的调用(正如@Serge 所建议的那样),可以完全防止多线程问题。请注意,锁对象是静态的,因此所有 DllWrapper 都使用相同的锁对象。

public class DllWrapper : IDisposable
{
     private static object _lockObject = new object();

     private int _apiHandle;
     private bool _isOpen;

     public void StartSession()
     {
         lock (_lockObject) {
             _apiHandle = ...; // TODO: open the session
         }
         _isOpen = true;
     }

     public void CloseSession()
     {
         const int MaxTries = 10;

         for (int i = 0; _isOpen && i < MaxTries; i++) {
             try {
                 lock (_lockObject) {
                     // TODO: close the session
                 }
                 _isOpen = false;
             } catch {
             }
         }
     }

     public void Dispose()
     {
         CloseSession();
     }
}

请注意,这些方法现在是实例方法。

现在您可以使用 using 语句确保会话关闭:

using (var session = new DllWrapper()) {
    try {
        session.StartSession();
        // TODO: work with the session
    } catch(ApplicationException ex) {
        // TODO: log the error
        // This is for exceptions not related to closing the session. If such exceptions
        // cannot occur, you can drop the try-catch completely.
    }       
} // Closes the session automatically by calling `Dispose()`.

您可以通过调用此类Session 以及方法OpenClose 来改进命名。这个类的用户不需要知道它是一个包装器。这只是一个实现细节。此外,方法的命名现在是对称的,无需重复名称Session

通过封装所有与会话相关的内容,包括错误处理、从错误情况中恢复和资源处理,您可以大大减少代码中的混乱。 Session 类现在是一个高级抽象。旧的DllWrapper 介于低级和高级之间。

【讨论】:

  • 谢谢。我非常喜欢你将它作为解决问题并同时整理代码的一种手段。使用这种方法的初始原型似乎大大减慢了处理速度,我不知道为什么。正在调查 - 如果我能解决问题,我很乐意接受。
猜你喜欢
  • 1970-01-01
  • 2011-08-31
  • 2014-11-27
  • 2017-03-27
  • 1970-01-01
  • 2020-08-24
  • 2015-09-05
  • 2011-06-01
相关资源
最近更新 更多