如何构造一个 try-catch-finally 块来处理 finally 中的错误?

How can I structure a try-catch-finally block to handle errors inside finally?

我在调用第三方 C++ dll 时遇到问题,我使用 DllImport 将其包装在 class 中以访问其函数。

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

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: One or more errors occurred. ---> System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

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

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

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

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

此外,将对 DLL 的调用包装在 lock 语句中(如 @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()`.

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

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