Мы столкнулись с трудностями при смешивании Задач с помощью нашего обработчика сбоев верхнего уровня и пытаемся найти обходной путь. Я надеюсь, что у кого-то есть идеи .
В наших инструментах есть обработчик сбоев верхнего уровня (из события UnhandledException в AppDomain), который мы используем для отправки отчетов об ошибках с помощью минидампов. Он отлично работает. К сожалению, Задачи решают эту проблему.
Мы только начали использовать 4. 0 Tasks и обнаружил, что внутри кода выполнения действия Task есть try / catch, который захватывает исключение и сохраняет его для передачи вниз по цепочке задач. К сожалению, наличие catch (Exception)
раскручивает стек, и когда мы создаем минидамп, сайт вызова теряется. Это означает, что у нас нет ни одной из локальных переменных во время сбоя, или они были собраны и т. Д.
Фильтры исключений кажутся правильным инструментом для этой работы. Мы могли бы обернуть некоторый код действия Task в фильтр с помощью метода расширения, а при возникновении исключения вызвать наш код обработчика сбоя, чтобы сообщить об ошибке с помощью минидампа. Однако мы не хотим делать это для каждого исключения, потому что в нашем собственном коде может быть try-catch, который игнорирует определенные исключения. Мы хотим создать отчет о сбое, только если catch
в Task собирался его обработать.
Есть ли способ пройти по цепочке обработчиков try / catch? Думаю, если бы я мог это сделать, я мог бы идти вверх в поисках ловушек до тех пор, пока не попаду в Task, а затем запустить обработчик сбоев, если это правда.
(Это похоже на дальний план, но я решил, что все равно спрошу.)
Или, если у кого-то есть идеи получше, я хотел бы их услышать!
ОБНОВЛЕНИЕ
Я создал небольшой пример программы, демонстрирующей проблему. Приношу свои извинения, я постарался сделать его как можно короче, но он все равно большой. : /
В приведенном ниже примере вы можете #define USETASK
или #define USEWORKITEM
(или ни одного), чтобы проверить один из трех вариантов.
В non -async case и случай USEWORKITEM, генерируемый минидамп строится на месте вызова, именно так, как нам нужно. Я могу загрузить его в VS и (после некоторого просмотра, чтобы найти нужный поток), я вижу, что у меня есть моментальный снимок, сделанный на сайте вызова. Замечательно.
В случае USETASK моментальный снимок берется из потока финализатора, который очищает Задачу. Это происходит спустя много времени после того, как исключение было сгенерировано, и поэтому получение минидампа на этом этапе бесполезно. Я могу выполнить Wait () для задачи, чтобы исключение было обработано быстрее, или я могу получить доступ к его исключению напрямую, или я могу создать минидамп из оболочки вокруг самого TestCrash, но все они по-прежнему имеют ту же проблему: уже слишком поздно, потому что стек был раскручен до той или иной ловушки.
Обратите внимание, что я намеренно включил в TestCrash команду try / catch, чтобы продемонстрировать, как мы хотим, чтобы одни исключения обрабатывались нормально, а другие перехватывались. USEWORKITEM и неасинхронные варианты работают именно так, как нам нужно. Задачи почти все делают правильно! Если бы я мог каким-то образом использовать фильтр исключений, который позволяет мне подниматься по цепочке try / 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);
}