【发布时间】:2019-06-12 10:41:26
【问题描述】:
我尝试在后台启动一些操作,我对其结果不感兴趣,但如果出现错误,我想记录它,并且 - 当然 - 阻止应用程序(这里:Windows 服务)崩溃。
public static void CreateAndStartTaskWithErrorLogging(Action _action, string _componentName, string _originalStacktrace = null)
{
DateTime started = HighPrecisionClock.Now;
Task task = new Task(_action);
task.ContinueWith(_continuation => _continuation.LogExceptions(_componentName, started, _originalStacktrace));
task.ConfigureAwait(false);
task.Start();
}
internal static void LogExceptions(this Task _t, string _componentName, DateTime _started, string _originalStacktrace = null)
{
try
{
_t.Wait(1000);
}
catch (Exception ex)
{
Logger.LogError(_componentName, $"An exception occurred in a fire-and-forget task which was started at {_started}.\r\n" +
$"The original stack trace is:\r\n{_originalStacktrace}");
Logger.LogException(_componentName, ex);
}
try
{
_t.Dispose();
}
catch (Exception dex)
{
Logger.LogException(_componentName, dex);
}
}
没有ConfigureAwait(false) 和没有_t.Dispose(),catch 工作并记录异常。但是应用程序在几秒钟后崩溃(即在 Finalizer 线程上?)。 Microsoft 事件查看器中的条目显示该异常。
使用ConfigureAwait 和_t.Dispose(),我在日志中看不到异常,应用程序只是崩溃了。
上面的想法有什么问题?
编辑:
同时我在没有ConfigureAwait 的情况下进行了测试,但使用了_t.Dispose。我可以捕获大约 10 个这样的异常,但没有一个会导致应用程序崩溃。这似乎解决了问题,但我不明白其中的原因,所以情况仍然很糟糕。
ConfigureAwait(false) 对任务中的异常(或在该任务中启动的任务,例如由 Parallel.ForEach 进一步向下)执行什么操作?
为什么Dispose - 在延续时调用,而不是根据评论正确的任务 - 防止崩溃(终结器不调用 Dispose,但 Dispose 可能设置一些影响其行为的标志)?
编辑 2:
这也不是一直有效,只有大部分时间。下面建议的解决方案 1 有时也会失败。
在崩溃上下文中,使用Utilities.TaskExtensions.CreateAndStartTaskWithErrorLogging(() => DataStore.StoreSyncedData(data), Name); 调用该函数,其中DataStore 设置为一个组合,该组合依次调用其成员上的Parallel.ForEach(m_InnerDataStores, _store => { _store.StoreSyncedData(_syncedData); });。其中一个使用 Accord 库编写视频,有时会在<Module>.avcodec_encode_video2(libffmpeg.AVCodecContext*, libffmpeg.AVPacket*, libffmpeg.AVFrame*, Int32*) 处导致AccessViolation,即异常可能来自非托管代码。
当然,我可以尝试在下面的某个地方捕捉它——但这不是这种方法的目标。我希望它能够在后台安全地运行任何代码而不会导致应用程序崩溃。
【问题讨论】:
-
不要猜测错误在哪里,发布完整的堆栈跟踪。
-
在您的情况下,
_t不是task,而是延续本身的任务,因此该任务自行处理。