为什么我不能在我的 Web API 项目中使用带有 C# 异步函数的丢弃作为即发即弃机制

Why can't I use a Discard with C# async function in my Web API project as a fire and forget mechanism

我正在编写一个 Web API 项目,对于我的一个终点(HandleAsync 方法是实现)我无法使用丢弃语法 (_)我不知道为什么。

我还有其他地方采用相同的模式:调用 CheckoutFileAsync 并且在 processNotifications 委托中我可以在调用 SendEmailAsync 时很好地使用 _

唯一的区别是 IfContentManagementAuthorizedAsync 而不是 IfFileActionAuthorizedAsync 的不同条目,但如果授权逻辑中一切正常,每个条目都有 'same' 异步回调的概念。

我收到的编译错误在这一行

_ = emailService.SendEmailAsync(

这是错误:

Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'bool'

如果我将鼠标悬停在 _ 上,智能感知会显示 (parameter) bool _。如果我将鼠标悬停在 _ 上,它会显示 (discard) Task<bool> _.

我基本上想要一个 'fire and forget' 机制来发送通知电子邮件。我不想处理结果或等待任何可能发生的延迟,据我所知,丢弃语法是我应该做的模式。鉴于下面的代码,您是否看到我不能使用丢弃语法的任何原因?

注意,我有两个 If*AuthorizedAsync 方法的重载,一个 returning Task<IActionResult> 和另一个 returning Task<ActionResult<T>> 因为它们用于我所有的端点都不同 return 类型,我无法弄清楚如何使一个签名适用于所有端点。

public async Task<IActionResult> HandleAsync( [FromQuery] FileIdParameters parameters )
{
    var fileInfo = await GetFilePropertiesAsync<DataLockerFile>( parameters.Id, null );

    if ( fileInfo == null )
    {
        return NotFound();
    }

    return await IfFileActionAuthorizedAsync(
        "Write",
        fileInfo.FolderName,
        async ( user, userEmail, canCreateFolder ) =>
            await CheckoutFileAsync(
                userEmail,
                fileInfo,
                dateTimeService.UtcNow,
                fileInfo =>
                {
                    _ = emailService.SendEmailAsync(
                        string.Join( ";", notificationEmails ),
                        emailService.DefaultFrom,
                        $"KAT Management Secure File Checkout: {fileInfo.FolderName}:{fileInfo.Name}",
                        $"{user} has checked out {fileInfo.FolderName}:{fileInfo.Name}."
                    );
                }
            )
    );
}

protected async Task<ActionResult<T>> IfFileActionAuthorizedAsync<T>(
    string requiredClaim, string folder,
    Func<string, string, bool, Task<ActionResult<T>>> validResult )
{   
    var claimResult = Foo(); // .. logic 
    return await validResult( claimResult.User, claimResult.UserEmail, claimResult.CanCreateFolder );
}
protected async Task<IActionResult> IfFileActionAuthorizedAsync(
    string requiredClaim, string folder,
    Func<string, string, bool, Task<IActionResult>> validResult )
{   
    var claimResult = Foo(); // .. logic 
    return await validResult( claimResult.User, claimResult.UserEmail, claimResult.CanCreateFolder );
}

protected async Task<IActionResult> CheckoutFileAsync(
    string userEmail,
    DataLockerBaseFile fileInfo,
    DateTime checkoutTime,
    Action<DataLockerBaseFile> processNotifications
)
{
    var fileInfo = await DoSomethingAsync(); // logic

    processNotifications( fileInfo );

    return Ok();
}

// This method is part of another class, the smtp configuration object is inject with DI
public async Task<bool> SendEmailAsync( string to, string from, string subject, string body )
{
    var message = CreateMailMessage( to, from, subject body );

    var mailClient = new SmtpClient( smtp.Server );
    mailClient.Credentials = new System.Net.NetworkCredential( smtp.UserName, smtp.Password );
    await mailClient.SendMailAsync( message );

    return true;
}

IntelliSense 悬停表示问题所在:

And if I hover over the _, intellisense says (parameter) bool _.

导致此问题的代码使用了一种通用模式来进行排序但并非真正丢弃的参数值,其中之一是 bool。例如,它可能有一些 (arg, _ /* unneeded boolean argument */) => { ... } 形式的 lambda。附带说明一下,您 post 编辑的代码 没有 具有此模式,并且不会导致问题;将代码简化为 post 时,请确保您最终 posting 的代码实际重现了问题。

这是表示“不需要此参数”的常见模式; 然而,它在语言层面(except in certain cases in C# 9.0)并不是真正的丢弃。相反,它实际上定义了一个名为 _ 的参数,类型为 bool.

因此,丢弃模式 _ = ... 不适用于该代码块。由于有一个名为 _ 的实际参数,编译器将其视为对该参数的赋值,并给出错误:

Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'bool'

解决方案是将 sort-of-discard 参数从 _ 重命名为类似 __ 的名称。然后丢弃模式将起作用。

旁注,"fire-and-forget" is almost always the wrong solution