Мы столкнулись с проблемой смешивания задач с нашим обработчиком аварийных ситуаций верхнего уровня и пытаемся найти обходной путь. Я надеюсь, у кого-то есть некоторые идеи.
В наших инструментах используется обработчик аварийных ситуаций верхнего уровня (из события AppDomain UnhandledException), который мы используем для создания отчетов об ошибках с помощью мини-дисков. Он работает чудесно. К сожалению, Задачи бросают в этом ключ.
Мы только начали использовать 4.0 Tasks и обнаружили, что внутри кода выполнения задачи Task есть try/catch, который захватывает исключение и сохраняет его для передачи вниз по цепочке задач. К сожалению, существование catch (Exception)
раскручивает стек, и когда мы создаем мини-накопитель, сайт вызова теряется. Это означает, что мы не имеем ни одной из локальных переменных во время сбоя, или они были собраны и т.д.
Фильтры исключений, по-видимому, являются правильным инструментом для этой работы. Мы могли бы обернуть некоторый код действия задачи в фильтр с помощью метода расширения, а в случае исключения, вызванного вызовом нашего кода обработчика сбоев, сообщить об ошибке с мини-помпой. Однако мы не хотим делать это при каждом исключении, потому что в нашем собственном коде может быть try-catch, который игнорирует определенные исключения. Мы хотим сделать отчет о сбое, если catch
в Task будет обрабатывать его.
Есть ли способ подойти к цепочке обработчиков try/catch? Я думаю, что если бы я мог это сделать, я мог бы подняться вверх, ища уловы, пока не нажмет "Задачу", а затем уволит обработчик аварии, если это правда.
(Это кажется длинным выстрелом, но я решил, что я все равно прошу.)
Или, если у кого-нибудь есть лучшие идеи, я бы хотел их услышать!
UPDATE
Я создал небольшую пробную программу, демонстрирующую проблему. Мои извинения, я постарался сделать это как можно короче, но он все еще большой.:/
В приведенном ниже примере вы можете #define USETASK
или #define USEWORKITEM
(или none) проверить один из трех вариантов.
В случае не-асинхронного и USEWORKITEM генерируемый minidump строится на сайте вызова точно так, как нам нужно. Я могу загрузить его в VS и (после некоторого просмотра, чтобы найти нужный поток), я вижу, что у меня есть моментальный снимок, сделанный на сайте вызова. Высокий.
В случае USETASK моментальный снимок извлекается из потока финализатора, который очищает задачу. Это долго после того, как исключение было выброшено, и поэтому захват мини-насоса в этот момент бесполезен. Я могу сделать Wait() в задаче, чтобы сделать исключение обработанным раньше, или я могу получить доступ к его Исключению напрямую, или я могу создать minidump из оболочки вокруг самого TestCrash, но все они все еще имеют одинаковую проблему: это слишком поздно, потому что стек был размотан до одного или другого.
Обратите внимание, что я преднамеренно поставил try/catch в TestCrash, чтобы продемонстрировать, как мы хотим, чтобы некоторые исключения обрабатывались нормально, а другие были пойманы. Случаи USEWORKITEM и non-async работают именно так, как нам нужно. Задачи почти все делают правильно! Если бы я мог каким-то образом использовать фильтр исключений, который позволяет мне подойти к цепочке try/catch (без фактического размотки), пока я не нажму на catch внутри Task, я мог бы сам сделать необходимые тесты, чтобы проверить, нужно ли мне запускать обработчик сбоя или нет. Отсюда мой оригинальный вопрос.
Здесь образец.
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
AppDomain.CurrentDomain.UnhandledException += (_, __) =>
{
using (var stream = File.Create(@"c:\temp\test.dmp"))
{
var process = Process.GetCurrentProcess();
MiniDumpWriteDump(
process.Handle,
process.Id,
stream.SafeFileHandle.DangerousGetHandle(),
MiniDumpType.MiniDumpWithFullMemory,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
}
Process.GetCurrentProcess().Kill();
};
TaskScheduler.UnobservedTaskException += (_, __) =>
Debug.WriteLine("If this is called, the call site has already been lost!");
// must be in separate func to permit collecting the task
RunTest();
GC.Collect();
GC.WaitForPendingFinalizers();
}
static void RunTest()
{
#if USETASK
var t = new Task(TestCrash);
t.RunSynchronously();
#elif USEWORKITEM
var done = false;
ThreadPool.QueueUserWorkItem(_ => { TestCrash(); done = true; });
while (!done) { }
#else
TestCrash();
#endif
}
static void TestCrash()
{
try
{
new WebClient().DownloadData("http://filenoexist");
}
catch (WebException)
{
Debug.WriteLine("Caught a WebException!");
}
throw new InvalidOperationException("test");
}
enum MiniDumpType
{
//...
MiniDumpWithFullMemory = 0x00000002,
//...
}
[DllImport("Dbghelp.dll")]
static extern bool MiniDumpWriteDump(
IntPtr hProcess,
int processId,
IntPtr hFile,
MiniDumpType dumpType,
IntPtr exceptionParam,
IntPtr userStreamParam,
IntPtr callbackParam);
}