将TPL与异步等待一起使用时无法添加日志记录

时间:2019-04-08 14:19:45

标签: c# logging async-await task-parallel-library

我一直在寻找这个问题的答案,但没有成功,因此请在这里尝试。 我正在尝试在任务中的代码中记录工作。 我试图尽可能简化我的示例。

    public void ExecuteRequestOnNewTask(string clientRequestContent)
    {
        WriteToTextFile("just entered ExecuteRequestOnNewTask");
        try
        {
            string response = string.Empty;
            Task.Factory.StartNew(
                async () =>
                {
                    WriteToTextFile("About to call ExecuteRequest");
                    response = await ExecuteRequest(clientRequestContent);
                    WriteToTextFile("Response received:" + response);
                });
        }
        catch (Exception ex)
        {
            WriteToTextFile("Exception from task:" + ex);
        }
    }

    public async Task<string> ExecuteRequest(string clientRequestContent)
    {
        // long running stuff here
    }

    public void WriteToTextFile(string text)
    {
        string textWithCurrentDateTime = "\r\n" + DateTime.UtcNow.AddHours(1) + ": " + text;
        string path = @"C:\Log.txt";
        if (!File.Exists(path))
        {
            File.Create(path);
            TextWriter tw = new StreamWriter(path);
            tw.WriteLine(textWithCurrentDateTime);
            tw.Close();
        }
        else if (File.Exists(path))
        {
            TextWriter tw = new StreamWriter(path, true);
            tw.WriteLine(textWithCurrentDateTime);
            tw.Close();
        }
    }

我在日志文件中得到的唯一输出是:

“刚输入的ExecuteRequestOnNewTask” “关于调用ExecuteRequest”

我没有记录响应,也没有长时间运行的方法。

2 个答案:

答案 0 :(得分:1)

代码有几个问题:

  • Task.Factory.StartNew是非常low-level, dangerous method。它具有一个非显而易见的默认任务计划程序,并且不了解async委托。如果要在线程池线程上运行代码,请改用Task.Run
  • StartNew返回的任务将被忽略。因此,catch块将永远不会做任何事情。如果您想在即发即弃的情况下捕获和记录异常,则需要在委托中完成。换句话说,您无法捕获由于正被遗忘而被触发并遗忘的事物的异常!

此外,WriteToTextFile也不是线程安全的,这在这里绝对是一个问题。但是,导致日志被截断的最可能原因是“一劳永逸”。如果您的应用程序退出,则一劳永逸的任务终止-无例外,无通知。这是设计使然-毕竟是一劳永逸

答案 1 :(得分:1)

如果您要记录即发即弃任务的异常,可以使用以下扩展方法:

public static Task OnExceptionLogError(this Task task, string message)
{
    task.ContinueWith(t =>
    {
        var exception = t.Exception;
        var innerExceptions = exception.Flatten().InnerExceptions;
        var lines = innerExceptions.Select(ex => $"{ex.GetType().FullName}: {ex.Message}");
        string literal = innerExceptions.Count == 1 ? "an exception" : $"{innerExceptions.Count} exceptions";
        WriteToTextFile($"{message}, {literal} occured on task #{task.Id}:\r\n  " + String.Join("\r\n  ", lines));
    }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
    return task;
}

用法示例:

Task.Factory.StartNew(
    async () =>
    {
        WriteToTextFile("About to call ExecuteRequest");
        response = await ExecuteRequest(clientRequestContent);
        WriteToTextFile("Response received:" + response);
    }).OnExceptionLogError("ClientRequestContent"); // <--- give a title for the error

例如通过调用Task.WhenAll记录所有可能汇总的嵌套错误。


这是从即发即忘任务中记录异常的另一种方法。它的优点是它在一处是全局处理程序,而缺点是除了异常本身之外,它无法记录有关错误的更多信息。

static void Main(string[] args)
{
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
    // ...
}

private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    // Log the e.Exception, which is an AggregateException
    WriteToTextFile("Exception from task:" + e.Exception);
}

更新:注意事项:

1)TaskScheduler.UnobservedTaskException事件是在垃圾回收任务时引发的,这可能发生得很晚或根本没有发生!

2)调试版本only on Release builds中不会引发此事件!