Обработка исключений в CLR в потоке, не созданном CLR
Проблема:
Необработанное исключение в потоке, входящем в CLR из неуправляемого кода, не запускает "нормальную" обработку CLR необработанного исключения.
В коде ниже звоню CSSimpleObject.GetstringLength()
из C++ с
- "1" генерирует исключение в вызывающем потоке (не созданный CLR поток),
- "2" создает исключение в новом потоке () (созданный CLR поток).
В случае "1"
- CurrentDomain_UnhandledException () никогда не вызывается.
- Домен приложения и процесс останутся загруженными и запущенными, вы получите только СБОЙ.
В случае "2" (ожидаемое поведение)
- CurrentDomain_UnhandledException () вызывается.
- Процесс убит.
Вопрос:
Что нужно сделать, чтобы получить "нормальное" поведение?
Образец кода:
Приведенный ниже код основан на примере кода Visual Studio 2010 " CppHostCLR" из " всех примеров взаимодействия и слияния".
RuntimeHost (C++):
PCWSTR pszStaticMethodName = L"GetStringLength";
PCWSTR pszStringArg = L"1";
//PCWSTR pszStringArg = L"2";
hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath,
pszClassName, pszStaticMethodName, pszStringArg, &dwLengthRet);
if (FAILED(hr))
{
wprintf(L"Failed to call GetStringLength w/hr 0x%08lx\n", hr);
goto Cleanup;
}
Управляемый код (C#):
public class CSSimpleObject
{
public CSSimpleObject()
{
}
//------8<----------
public static int GetStringLength(string str)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
switch (str)
{
case "1":
throw new Exception("exception in non-CLR-created thread");
case "2":
Thread thread = new Thread(new ThreadStart(WorkThreadFunction));
thread.Start();
break;
}
return str.Length;
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine("CurrentDomain_UnhandledException:" + e.ToString());
Console.ReadKey();
}
public static void WorkThreadFunction()
{
throw new Exception(""exception in CLR-created thread"");
}
Исследования до сих пор:
MSDN изначально подразумевает, что необработанные исключения в потоке, не созданном CLR, должны вести себя более или менее "естественно" - см. " Исключения в управляемых потоках"
Общеязыковая среда выполнения позволяет естественным образом обрабатывать большинство необработанных исключений в потоках. В большинстве случаев это означает, что необработанное исключение приводит к закрытию приложения. "
"Большинство" означает, что во внутренних потоках, созданных в среде CLR, исключение потока и исключение выгруженного исключения из Application Domain обрабатываются по-разному. В не-CLR потоках
"они работают нормально, что приводит к прекращению заявки".
Дальнейшие исследования привели меня к " Необработанной обработке исключений в CLR", где я обнаружил следующее:
"если исключение не было обработано... в управляемом методе исключение вышло бы из CLR, но продолжало распространяться вверх по стеку как собственное исключение SEH (управляемые исключения представлены как собственные исключения SEH) ... Необработанное исключение ОС Механизм фильтра (UEF) не всегда может приводить к запуску обработки необработанных исключений в CLR. При нормальных обстоятельствах это будет работать так, как ожидается, и обработка необработанных исключений в CLR будет запускаться. Однако в некоторых случаях это может не произойти ".
Что не так с приведенным выше кодом или как его можно изменить, чтобы запустить обработку необработанных исключений в CLR?
Обновление (2011-05-31):
Я только что нашел старый отчет об ошибке: "UnhandledExceptionEventHandler не вызывается, когда управляемый код вызывается из неуправляемого и выдает исключение - http://tinyurl.com/44j6gvu", где Microsoft подтверждает, что это "ошибочное" поведение:
Спасибо, что нашли время сообщить об этой проблеме. Такое поведение действительно является ошибкой, вызванной механизмом выполнения CLR и CRT, конкурирующим за UnhandledExceptionFilter. Архитектура CLR была пересмотрена в версии 4.0, поддерживающей этот сценарий.
Обновление (2011-06-06):
Почему важно сделать это правильно?
- если вы создаете хостинг-среду, ваши разработчики ожидают последовательного поведения при обработке исключений
- если нет способа вызвать "обычную обработку исключений CLR" в собственном потоке, это означает, что вы всегда должны передавать выполнение в управляемый поток (например, в очередь в пуле потоков)
- все еще есть небольшой кусочек кода, передающий исключение из нативного в управляемый поток... который должен перехватывать все исключения и каким-то образом обрабатывать эту ситуацию
Примечание: изменение поведения CLR через SetActionOnFailure()
еще хуже то, что в конечном итоге он маскирует исходное исключение (т. е. вместо нехватки памяти вы в конечном итоге видите threadAborts - без подсказки, откуда исходная ошибка распределяется)!
1 ответ
Обновление подразумевает, что у вас, возможно, есть решение здесь. Тем не менее, его решение не будет работать во всех случаях, так что здесь немного больше информации.
Если вы предпочитаете поведение CLR Unhandled Exception, сделайте это самой внешней программой и вызывайте собственный код только для выполнения определенных функций. Это гарантирует, что CLR получит контроль над фильтром необработанных исключений.
Если вы хотите сохранить свою текущую структуру, а у вас небольшой код C++, вы можете вообще отказаться от использования CRT (что лишит вас кучу полезных вещей, включая статические конструкторы и обработку исключений C++). Это гарантирует, что CLR получит фильтр необработанных исключений.
И, конечно же, вы можете просто вызвать SetUnhandled Exception Filter и получить желаемое поведение.
Тем не менее, я думаю, что лучшая рекомендация в этой ситуации - поместить фактическую функцию с блоком catch в стек вызовов любого потока, где вы хотите что-то делать, когда происходит исключение, и не полагаться на механизм UEF - потому что в контексте компонентной системы, она всегда хрупкая, так как за нее борются несколько пользователей.
Martyn