#c# #multithreading #c#-4.0 #exception-handling #task-parallel-library
#c# #многопоточность #c #-4.0 #исключение #task-parallel-library
Вопрос:
У меня есть процесс, который создает мини-дампы при возникновении необработанных исключений (используя оба AppDomain.CurrentDomain.UnhandledException
и TaskScheduler.UnobservedTaskException
). Дамп отлично работает из обработчика необработанных исключений, но AccessViolationException
при вызове из обработчика исключений ненаблюдаемой задачи выдает ошибку (см. Пример кода ниже).
Из того, что я прочитал в MSDN и StackOverflow, ключевое различие между двумя путями кода заключается в том, что последний происходит из потока финализатора, и я предполагаю, что состояние потока или, возможно, безопасность препятствуют успешному выполнению этой операции.
Есть ли у кого-нибудь какая-либо информация или ссылки, объясняющие, почему операция мини-дампа завершается неудачно в случае исключения ненаблюдаемого потока? Я рад признать, что то, что я пытаюсь, просто невозможно, но я действительно хотел бы подтвердить это и знать, почему…
Приведенный ниже код демонстрирует мою проблему, вызывая необработанное исключение внутри задачи и заставляя сборку мусора запускать исключение ненаблюдаемого потока. Затем MiniDumpWriteDump
вызов генерирует AccessViolationException
.
using System;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
namespace ExceptionTest
{
public class ClassThatThrows
{
public void Throw()
{
var t = new Task(() =>
{
Console.WriteLine("Throwing...");
throw new NullReferenceException();
});
t.Start();
}
}
public class Program
{
public static void Main(string[] args)
{
TaskScheduler.UnobservedTaskException = HandleUnobservedTaskException;
var test = new ClassThatThrows();
test.Throw();
Thread.Sleep(1000);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
[HandleProcessCorruptedStateExceptions]
[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
[SecurityCritical]
private static void HandleUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
WriteMiniDump();
}
private static void WriteMiniDump()
{
var miniDumpFilePath = GetMiniDumpFilePath();
using (var fileStream = new FileStream(miniDumpFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
MiniDumpExceptionInformation exceptionInfo;
exceptionInfo.ThreadId = GetCurrentWin32ThreadId();
exceptionInfo.ClientPointers = false;
exceptionInfo.ExceptionPointers = Marshal.GetExceptionPointers();
var currentProcess = GetCurrentProcess();
var currentProcessId = GetCurrentProcessId();
var safeFileHandle = fileStream.SafeFileHandle;
MiniDumpWriteDump(currentProcess, currentProcessId, safeFileHandle.DangerousGetHandle(), 0x00000000, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero);
}
}
protected static string GetMiniDumpFilePath()
{
var timestamp = DateTime.Now.ToString("yyyyMMddTHHmmss");
var fileName = string.Format("MiniDump.{0}.mdmp", timestamp);
return Path.Combine(Path.GetTempPath(), fileName);
}
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
public static extern uint GetCurrentWin32ThreadId();
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, IntPtr hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);
[DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess", ExactSpelling = true)]
public static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", EntryPoint = "GetCurrentProcessId", ExactSpelling = true)]
public static extern uint GetCurrentProcessId();
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct MiniDumpExceptionInformation
{
public uint ThreadId;
public IntPtr ExceptionPointers;
[MarshalAs(UnmanagedType.Bool)]
public bool ClientPointers;
}
}
}
Спасибо
Комментарии:
1. Вы пробовали закрепить
IntPtr
передаваемое?2. Нет повторения, никаких очевидных ошибок, которые я вижу. Окна, dbghelp.dll и версии .NET должны быть задокументированы в вопросе. Все, что я могу порекомендовать, это добавить
GC.KeepAlive(e.Exception);
в обработчик событий, однако выстрел на большое расстояние. Ищите проблемы с окружающей средой. И включите неуправляемую отладку, сервер символов Microsoft и опубликуйте трассировку стека, которую вы видите, когда она бомбит.
Ответ №1:
Я попытался реализовать предложение от Yuval со своего домашнего компьютера и не смог воспроизвести проблему, что сразу же заставило меня рассмотреть версию dbghelp.dll на оригинальной машине в процессе работы.
После ввода dbghelp.dll из последней версии Windows 8.1 SDK в моей исполняемой папке проблема исчезла, поэтому я могу указать пальцем на устаревшую версию dbghelp.dll и не какая-то непонятная проблема C # с созданием мини-дампов из потока финализатора.
Для справки, dbghelp.dll 6.1.7601.17514 демонстрирует проблему и dbghelp.dll 6.3.9600.17029 подтвержден для устранения проблемы. Стек вызовов из исключения (хотя и не раскрывает многого) показан ниже:
ntdll.dll!NtWaitForSingleObject()
KernelBase.dll!WaitForSingleObjectEx()
msvcr110_clr0400.dll!__C_specific_handler()
ntdll.dll!RtlpExecuteHandlerForException()
ntdll.dll!RtlDispatchException()
ntdll.dll!KiUserExceptionDispatch()
dbghelp.dll!MiniDumpWriteDump()
[Managed to Native Transition]
ExceptionTest.exe!ExceptionTest.Program.WriteMiniDump() Line 66 C#
ExceptionTest.exe!ExceptionTest.Program.HandleUnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e) Line 48 C#
mscorlib.dll!System.Threading.Tasks.TaskScheduler.PublishUnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs ueea)
mscorlib.dll!System.Threading.Tasks.TaskExceptionHolder.Finalize()