【问题标题】:hosting clr and catching threading exceptions托管 clr 并捕获线程异常
【发布时间】:2011-12-15 07:42:16
【问题描述】:

我正在尝试编写一个可以加载托管插件的插件系统。如果有任何异常,主机应该能够卸载插件。 对于我的 poc,我在 C# 中有一个示例代码库,它会抛出这样的异常......

 public static int StartUp(string arguments)
 {
       Console.WriteLine("Started exception thrower with args {0}", arguments);
       Thread workerThread = new Thread(() => 
            {
                Console.WriteLine("Starting a thread, doing some important work");
                Thread.Sleep(1000);
                throw new ApplicationException();
            }
         );
         workerThread.Start();
         workerThread.Join();
         Console.WriteLine("this should never print");
        return 11;
    }

然后我有像这样的本机 win32 控制台应用程序..

int _tmain(int argc, _TCHAR* argv[])
{
    ICLRMetaHost *pMetaHost       = NULL;
    HRESULT hr; 
    ICLRRuntimeInfo *runtimeInfo = NULL;    
    __try
    {
        hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
        hr = pMetaHost->GetRuntime(L"v4.0.30319",IID_ICLRRuntimeInfo,(LPVOID*)&runtimeInfo);
        ICLRRuntimeHost *runtimeHost  = NULL;
        hr = runtimeInfo->GetInterface(CLSID_CLRRuntimeHost,IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);    
        ICLRControl* clrControl = NULL;
        hr = runtimeHost->GetCLRControl(&clrControl);
        ICLRPolicyManager *clrPolicyManager = NULL;
        clrControl->GetCLRManager(IID_ICLRPolicyManager, (LPVOID*)&clrPolicyManager);
        clrPolicyManager->SetDefaultAction(OPR_ThreadAbort,eUnloadAppDomain);   
        hr = runtimeHost->Start();
        DWORD returnVal = NULL;         
        hr = runtimeHost->ExecuteInDefaultAppDomain(L"ExceptionThrower.dll",L"ExceptionThrower.MainExceptionThrower",L"StartUp",L"test",&returnVal);        
        runtimeHost->Release();
    }
    __except(1)
    {
        wprintf(L"\n Error thrown %d",e);
    }
    return 0;
}

问题是如果我使用上面的代码,主机将完成运行托管代码(“这不应该打印”行最终会打印) 如果我删除 clrPolicyManager->SetUnhandledExceptionPolicy(eHostDeterminedPolicy),那么主机进程会崩溃。

可以在非托管主机中进行任何操作以从运行时优雅地删除错误的应用程序并继续工作吗?

【问题讨论】:

  • 您的代码启用了 .NET 1.x 异常处理策略。这只是终止线程。不是您想要的,您还需要调用 ICLRPolicyManager::SetDefaultAction() 来告诉它在线程中止时卸载应用程序域。你在某个地方还有一个死线程,使用 __try/__catch 来捕获异常。
  • 我添加了以下行 clrPolicyManager->SetDefaultAction(OPR_ThreadAbort,eUnloadAppDomain);到代码,我更新了代码,但效果一样,宿主进程还是崩溃
  • 您可能错过了评论中的“死线”部分。您必须捕获 SEH 异常。异常代码为 0xe0434f4d。 msdn.microsoft.com/en-us/library/s58ftw19%28v=VS.100%29.aspx

标签: c# clr clr-hosting


【解决方案1】:

看起来像添加以下内容和 SetDefaultAction 可以解决崩溃:

clrPolicyManager->SetUnhandledExceptionPolicy(EClrUnhandledException::eHostDeterminedPolicy);

【讨论】:

  • 如问题“问题是如果我使用上面的代码,主机将完成运行托管代码”(“这不应该打印”行将最终打印)如果我删除clrPolicyManager->SetUnhandledExceptionPolicy(eHostDeterminedPolicy),那么宿主进程就会崩溃。"
【解决方案2】:

您可以专门为每个给定插件启动一个新的 AppDomain 并在其中启动它。见http://msdn.microsoft.com/en-us/library/ms164323.aspx

每个 AppDomain 都是一个可以执行代码的隔离环境。一个 AppDomain 中发生的异常可以与其他异常隔离。见:http://msdn.microsoft.com/en-us/library/system.appdomain(v=VS.100).aspx

【讨论】:

  • 域确实提供了内存/安全沙箱,但它们不提供线程隔离,即线程是在 CLR 级别创建的,它们可以在任何域中执行,因此如果线程上发生未处理的异常,整个 CLR 崩溃了……
  • @np-hard - 请参阅 msdn:“使用应用程序域来隔离可能导致进程中断的任务。如果正在执行任务的 AppDomain 的状态变得不稳定,则可以卸载 AppDomain 而不会影响进程。当进程必须长时间运行而不重新启动时,这一点很重要。您还可以使用应用程序域来隔离不应共享数据的任务。 (msdn.microsoft.com/en-us/library/system.appdomain.aspx)
  • @np-hard - 请阅读:ikickandibite.blogspot.com/2010/04/…,它可以解决您的问题。我不确定我们是否可以使用 CLR-Hosting API 复制它。如果没有,您可以为插件 dll 开发一个托管引导程序,它可以优雅地处理未处理的异常
  • 有异常会破坏整个进程的状态,比如StackOverflowException。从 .NET 2.0 开始,这些会降低整个过程,无论它们是否位于单独的应用程序域中。防止这种情况的唯一方法是通过 CLR 托管应用程序中的ICLRPolicyManager,请参阅:msdn.microsoft.com/en-us/library/ms164394.aspx
【解决方案3】:

【讨论】:

  • 裸链接不能提供好的答案。请您在此处总结这篇文章。如果链接的内容曾经移动,这个答案变得比无用更糟糕。此外,您无需在所有答案上签名,它们附有您的天赋,这就是您的签名。
【解决方案4】:

首先,如果你想用上面的代码防止应用崩溃,你需要使用SetUnhandledExceptionFilter,像这样:

LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionInfo)
{
    // do something useful
    return EXCEPTION_EXECUTE_HANDLER; // prevent crash
}

int _tmain(int argc, _TCHAR* argv[])
{
    SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
        ...
}

但这可能不是你真正想要的。一种解决方案(我相信由 Polity 提出)是创建一个可以轻松捕获所有未处理异常的中间 AppDomain。您可以在 C# 中执行此操作,如下所示:

public class PluginVerifier
{
    public static int CheckPlugin(string arguments)
    {
        AppDomain appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
        appDomain.UnhandledException += AppDomainUnhandledException;
        object obj = appDomain.CreateInstanceAndUnwrap("ExceptionThrower", "ExceptionThrower.MainExceptionThrower");
        object ret = obj.GetType().InvokeMember("Startup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, obj, new object[] { arguments });
        AppDomain.Unload(appDomain);
        return (int)ret;
    }

    private static void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        AppDomain appDomain = (AppDomain)sender;
        // the following will prevent "this should never print" to happen
        AppDomain.Unload(appDomain);
    }
}

但是,要使其能够正常工作,您需要对插件类进行两项更改:

  • 它们必须派生自 MarshalByRefObject
  • 插件方法不能是静态的(静态方法调用不经过 AppDomain 过滤器)

所以你的类应该是这样写的:

public class MainExceptionThrower: MarshalByRefObject
{
    public int StartUp(string arguments)
    {
    ...
    }
 }

如果您这样做,您可以删除对 SetUnhandledExceptionPolicy、SetActionOnFailure 或 SetDefaultAction 的调用,只需像这样替换引导代码:

    hr = runtimeHost->ExecuteInDefaultAppDomain(L"PluginSystem.dll", L"PluginSystem.PluginVerifier", L"CheckPlugin", L"test", &returnVal);        

如果您使用上面的启动代码尝试此操作,此调用将返回 hr=0x80131604,即 COR_E_TARGETINVOCATION (TargetInvocationException)。

【讨论】:

    猜你喜欢
    • 2011-09-01
    • 2018-12-17
    • 2011-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多