我尝试在后台启动某些操作,我对其结果不感兴趣,但是如果发生错误,我希望将其记录下来,并且-当然-阻止该应用程序(此处为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 Event Viewer中的条目显示该异常。
使用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库编写视频,有时会在AccessViolation
处引起<Module>.avcodec_encode_video2(libffmpeg.AVCodecContext*, libffmpeg.AVPacket*, libffmpeg.AVFrame*, Int32*)
,即异常可能来自非托管代码。
当然,我可以尝试将其捕获到此处的某处-但这不是此方法的目标。我希望它能够在后台安全地运行任何代码而不会导致应用程序崩溃。
答案 0 :(得分:1)
这是我对记录错误的建议:
public static void OnExceptionLogError(this Task task, string message)
{
task.ContinueWith(t =>
{
// Log t.Exception
}, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
}
用法示例:
var task = Task.Run(action);
task.OnExceptionLogError("Oops!");
try
{
await task;
}
catch
{
// No need to log exception here
}