【问题标题】:Omit parameters in wrapped inner async methods?在包装的内部异步方法中省略参数?
【发布时间】:2022-01-10 10:21:57
【问题描述】:

根据 SonarQube 代码规则S4457 的要求,将异步方法包装在同步部分进行参数检查可能很快变得难以阅读。使用 C# 本地方法,可以省略本地方法的参数。

这种参数的省略对编译器和本地方法有什么缺点或问题吗?

public Task<MyResult> GetMyResultAsync(string myId, string someOption, CancellationToken cancellationToken)
{
    // check arguments synchronously, before entering async context:
    myId = myId ?? throw new ArgumentNullException(nameof(myId));
    someOption = someOption ?? throw new ArgumentNullException(nameof(someOption));
    return InnerAsync();

    // don't replicate the arguments, use from outer method.
    async Task<MyResult> InnerAsync()
    {
        using var httpClient = _httpClientFactory.CreateClient(CfgName);
        var response = await httpClient.GetAsync(
                CreateUrl(myId, someOption), cancellationToken)
            .ConfigureAwait(false);
        return ParseResponse<MyResult>(response);
    }
} 

相比:

public Task<MyResult> GetMyResultAsync(string myId, string someOption, CancellationToken cancellationToken)
{
    // check arguments synchronously, before entering async context:
    myId = myId ?? throw new ArgumentNullException(nameof(myId));
    someOption = someOption ?? throw new ArgumentNullException(nameof(someOption));
    return InnerAsync(myId, someOption, cancellationToken);

    // new arguments for inner async method:
    async Task<MyResult> InnerAsync(
       string myIdInner, string someOptionInner, 
       CancellationToken cancellationTokenInner)
    {
        using var httpClient = _httpClientFactory.CreateClient(CfgName);
        var response = await httpClient.GetAsync(
                CreateUrl(myIdInner, someOptionInner), 
                cancellationTokenInner)
            .ConfigureAwait(false);
        return ParseResponse<MyResult>(response);
    }
} 

背景:规则要求参数检查,抛出Argument(Null/OutOfRange)Exception,在进入异步上下文之前同步发生,这样这些异常在发生火灾时不会丢失——然后忘记;它们甚至会在异步任务创建之前被抛出。这些方法仍被命名为“-Async”,因为它们在应用规则之前。

我知道异步方法基本上被编译成状态机,以局部变量作为属性,所以我不希望使用外部变量有任何缺点。对吗?

【问题讨论】:

  • See here。捕获 args 的版本稍微多一些工作,但在 async 代码的上下文中它是非常微不足道的
  • 您是在编写库还是应用程序代码的一部分?如果您不编写库,恕我直言,您可以忽略此规则。
  • 它是公司库的一部分,包含各种 REST 客户端。我真的不喜欢这条规则,因为许多应用程序的编写方式都期望异步方法真正异步,包括参数检查。含义:火与忘记不应该扔;如果 AggregateException 被抛出(.Wait(), .Result),ArgumentException 也应该包含在其中。
  • @ErikHart:IMO,参数检查是一种boneheaded exception,永远不应该被抓住。因此,它们是否被包裹在 AggregateException 中并不重要,它们是否被同步抛出也没关系。由于调用代码不应该捕获它们,因此它不应该对它们的抛出方式有任何期望。国际海事组织。 :)

标签: c# methods async-await arguments


【解决方案1】:

如果您将鼠标悬停在 Visual Studio 中的 InnerAsync local function 上,您将看到捕获的变量:

这意味着 C# 编译器必须创建一个 invisible autogenerated struct 的实例,将这些变量保存为字段。请注意,自动生成的结构的字段是可变的。除此之外,捕获变量以简化代码没有任何问题。

如果你因为某种原因不想使用这个语言特性,一定要标记你的本地函数as static,以免你不情愿地错误地创建闭包。

【讨论】:

  • 注意它会生成一个内部结构(除非它因为其他原因需要是一个类),所以没有GC压力
  • @canton7 you are right,它创建了一个由ref传递的struct
  • 它必须是一个类for async methods,或者如果它是used as a delegate等。但如果可以的话,它使用一个结构。
  • 在 Visual Studio 鼠标悬停文本中观看捕获的好提示。我现在注意到,在使用 dotPeek 反编译器时,在本地函数中捕获外部变量会创建一个 additional 显示类,该变量作为字段,然后在异步方法的状态机类中。然后通过状态机中的“this”引用来引用变量/字段。我没有注意到本地示例中的任何结构,只有密封类 - 可能因 .NET 版本而异。
  • @ErikHart 确保在发布模式下编译您的项目,以查看真实情况。
猜你喜欢
  • 2018-11-01
  • 2022-10-14
  • 1970-01-01
  • 2018-10-19
  • 2021-12-15
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
  • 2014-12-30
相关资源
最近更新 更多