在即发即弃任务中记录异常

时间:2019-06-12 10:41:26

标签: c# exception task

我尝试在后台启动某些操作,我对其结果不感兴趣,但是如果发生错误,我希望将其记录下来,并且-当然-阻止该应用程序(此处为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*),即异常可能来自非托管代码。

当然,我可以尝试将其捕获到此处的某处-但这不是此方法的目标。我希望它能够在后台安全地运行任何代码而不会导致应用程序崩溃。

1 个答案:

答案 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
}