【问题标题】:Overloading generic and void Task type重载泛型和空任务类型
【发布时间】:2017-09-06 12:35:57
【问题描述】:

我做了这个 hacky 变通方法来为我的一些异步 api 调用添加前/后日志记录。

是否有更简洁/更好的方法来编写 void Task 的重载?

这种方法一般有什么问题吗?有任何建议的更改吗?

public static Task<TResult> LogAsyncTask<TResult>
(this Task<TResult> task, string messageTemplate, 
   LogEventLevel level = LogEventLevel.Information)
{
  Log.Write(level, "Async Starting: " + messageTemplate);
  return task.ContinueWith(antecedent =>
  {
    var result = antecedent.GetAwaiter().GetResult();
    Log.Write(level, "Async Finished: " + messageTemplate);
    return result;
  });
}

public static Task<object> LogAsyncTask
(this Task task, string messageTemplate, 
   LogEventLevel level = LogEventLevel.Information)
 => Task<object>.Factory.StartNew(() =>
  {
    task.GetAwaiter().GetResult();
    return null;
  }).LogAsyncTask(messageTemplate, level);

你是这样称呼它的:

await apiClient.MySoapMethodAsync().LogAsyncTask("Doing MySoapMethod...");

【问题讨论】:

  • 我看到这段代码的一个明显问题是,就其本质而言,它必须是谎言。如果您正在使用热任务调用方法(您的代码假定它只调用ContinueWith 而不是Start),那么当您输出“Starting”时,该任务已经开始,甚至可能实际上已经开始已经完成了。
  • Fildor,它使用 serilog。达米安,是的,我对此很好,这主要用于可能最终需要 1 秒以上的肥皂电话,只是为了跟踪任何长时间运行的电话。我知道日志行可能会在方法启动后被调用,但通常不会在我们的案例中对时间产生实质性影响。
  • 是的,我以为我看到了一个语法错误,但那是我的老脏眼睛......你只想测量往返时间还是你也想插入某种时间 -以后会出功能吗?
  • 这纯粹是为了测量。我什至使用 awaiter 以便它会重新抛出任何错误。

标签: c# task overloading extension-methods


【解决方案1】:

正如 Damien 正确指出的那样,您的代码存在一个问题,即它只会在异步方法的同步部分完成(并返回一个任务)。一个合适的解决方案是使用 Fody 或 PostSharp 等面向方面的框架;您可以从 my AsyncDiagnostics project 开始,它的作用非常相似。

如果您确实保留当前方法,则不应使用ContinueWithStartNew。此外,您应该确保写入最终日志,无论是否有异常:

public static async Task<TResult> LogAsyncTask<TResult>(
    this Task<TResult> task, string messageTemplate,
    LogEventLevel level = LogEventLevel.Information)
{
  Log.Write(level, "Async Starting: " + messageTemplate);
  try
  {
    return await task.ConfigureAwait(false);
  }
  finally
  {
    Log.Write(level, "Async Finished: " + messageTemplate);
  }
}

public static Task LogAsyncTask(
    this Task task, string messageTemplate, 
    LogEventLevel level = LogEventLevel.Information)
{
  return LogAsyncTask<object>(async () =>
  {
    await task.ConfigureAwait(false);
    return null;
  }, messageTemplate, level);
}

【讨论】:

    【解决方案2】:

    正如其他答案/cmets 所提到的,您当前的代码存在一个核心问题,即您直到它启动后的某个时间才表明它正在启动,甚至可能在它完成之后。不过有一个简单的解决方案,那就是接受一个返回 Task 的方法,而不是 Task 本身,这样调用该方法就会启动异步操作。

    正如斯蒂芬所说,您真的不应该在这里使用ContinueWith。很难获得用于传播任务状态的正确语义(您并没有完全正确)并且使用await 只是更容易和更清晰。

    请注意,尝试根据另一种方法实现一种方法(您可以根据另一种方法实现其中一种方法)并不值得在这里付出努力。这两种方法都如此简单,对于泛型和非泛型版本从头开始正确实现就像调用另一个重载一样容易,如果不是更容易的话,所以我不会不要打扰。此外,如果您经常调用这样的方法(看起来您会这样),那么不调用不必要代码的非常小的性能优势可能是值得的,因为成本如此之低。

    public static async Task<TResult> LogAsyncTask<TResult>(
        Func<Task<TResult>> asyncMethod, string messageTemplate,
        LogEventLevel level = LogEventLevel.Information)
    {
        Log.Write(level, "Async Starting: " + messageTemplate);
        try
        {
            return await asyncMethod().ConfigureAwait(false);
        }
        finally
        {
            Log.Write(level, "Async Finished: " + messageTemplate);
        }
    }
    
    public static async Task LogAsyncTask(
        Func<Task> asyncMethod, string messageTemplate,
        LogEventLevel level = LogEventLevel.Information)
    {
        Log.Write(level, "Async Starting: " + messageTemplate);
        try
        {
            await asyncMethod().ConfigureAwait(false);
        }
        finally
        {
            Log.Write(level, "Async Finished: " + messageTemplate);
        }
    }
    

    【讨论】:

      【解决方案3】:

      这个想法看起来很有趣。 您还可以查看 Aspect Oriented Programming 来解决您的问题。 我试图做一些非常相似的事情。

      查看文章Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class,然后查看我的文章Aspect Oriented Programming in C# with RealProxy 如何修复第一篇文章中的示例以处理任务结果。即使您决定不使用 AOP,您也可以将我在第二篇文章中的代码应用到您的解决方案中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-31
        • 2018-08-08
        • 1970-01-01
        • 2017-05-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多