在库中的异步方法中,总是使用'async'和'await'关键字吗?

16

摘要: 在一个库方法中,何时应该使用asyncawait关键字而不是直接返回Task?

我认为我的问题与这个问题有关。然而,那个问题是关于.NET 4.0和TPL的,而我正在使用带有asyncawait关键字的.NET 4.6。因此,我认为我的问题可能会得到不同的答案,因为这些关键字在回答链接的问题时不存在。

说明:我正在为外部WCF服务编写一个简单的包装器,该包装器进行多个SendAsync调用。现在我认为每个包装器方法都应该直接返回Task<>而不被等待。我的理解是应该在应用程序层使用async/await,而不是在库中使用。

所以,例如,这是我认为我应该采取的每个包装器方法的方法:

private Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return _service.SendAsync(request);
}

但是在互联网上,我发现有几篇文章使用了这种方法:

private async Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return await _service.SendAsync(request).ConfigureAwait(false);
}

这里是我在technet上找到的另一个例子:

async Task PutTaskDelay()
{
    await Task.Delay(5000);
} 

private async void btnTaskDelay_Click(object sender, EventArgs e)
{
    await PutTaskDelay();
    MessageBox.Show("I am back");
}

那么,什么时候应该使用第二种方法(包括asyncawait关键字)?为什么不只返回整个 Task ,而不让PutTaskDelay成为async呢?我认为只要可能就直接返回Task,并且仅在应用程序层使用async/await来获得最终结果。如果我错了,那么这两种方法之间有什么区别?

我的担忧: 当使用asyncawait关键字时,似乎它只是为编译器提供额外的工作而没有任何好处。


1
async/await是您方法的实现细节。对于您的调用方来说,声明async Task Method()或仅Task Method()并不重要。事实上,您可以在以后的时间点之间自由切换这两个选项,而不被视为破坏性更改。 - Damien_The_Unbeliever
@Alex - 我认为你的问题是一个很好的问题,我对它进行了相当多的编辑,希望能使它对其他人更有用,并引起更多注意。我认为我忠实地保留了你问题的意图...如果不是,请纠正我可能存在的误解。此外,在你的代码示例中,我将SignAsync改为SendAsync。希望这符合你的意图。 - DavidRR
1
@DavidRR 谢谢您的编辑。不,当我写 SignAsync 时,我指的是 async Sign 方法,因为我调用它在某些认证中心为字节数组获取数字签名。但我不会编辑这篇文章,因为方法名称 SignSend 并不重要。我认为意图实际上得到了保留,所以我只是感谢您做得很好 :) - Alex Zhukovskiy
1
@Alex - 我完全同意SendAsyncSignAsync的使用并不是你问题的核心。我们的评论应该能够消除任何可能因为寻找SendAsyncSignAsync而产生的困惑。 - DavidRR
显示剩余4条评论
2个回答

9

我应该在库中使用 async await 吗?

这取决于情况。如果你想利用异步编程范式,那么答案是“是”,大多数情况下都需要使用 asyncawait 关键字。很可能,你会发现自己需要使用 async/await。这是因为在大多数情况下,仅使用 TaskTask<T> 是困难的,因为你很可能需要考虑所调用的异步操作的结果。

此外,根据你的问题,似乎你对这些关键字本身以及它们与 TaskTask<T> 类型的关系有些困惑。让我为你澄清一下。

async 关键字允许方法使用 await 关键字。最佳实践是除非无法(例如,如上所示的按钮单击事件处理程序),否则所有异步方法都应返回 TaskTask<T>

返回 TaskTask<T> 的方法表示异步操作。当您在库中时,建议始终使用 .ConfigureAwait(false),详细原因请参见此处。此外,我总是向人们指出这篇详细文章

要区分问题中的两种方法:

以下方法返回一个Task<SignResponse>。这是一个异步操作,代表着登录的工作。调用者可以等待该方法以获取SignResponse
private Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return _service.SignAsync(request);
}

同样,这个版本做的事情也一样...只是不需要使用 async/await 关键字。它们不需要的原因是该方法本身不需要使用 SignResponse,因此它可以像上面所示一样简单地返回 Task<SignResponse>。正如您在问题中指出的那样,当您不需要使用 async/await 关键字时,确实存在惩罚。这样做会添加一个额外的状态机步骤,因为结果被产生了,因为它被等待。
private async Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    return await _service.SignAsync(request).ConfigureAwait(false);
}

最后,如果您需要对响应进行推理,可以像这样使用上述关键字:

private async Task<SignResponse> GetSignDataAsync(SigningRequestType request)
{
    var result = await _service.SignAsync(request).ConfigureAwait(false);
    if (result.SomeProperty == SomethingWeCareToCheck)
    {
        _log.Log("Wow, this was un-expected...");
    }
    return result;
}

2
可能值得一提的是,有一个功能请求,希望编译器在处理return await(在方法中仅有的await)时变得更加智能。 - Damien_The_Unbeliever
简而言之,我应该尽可能使用直接任务返回,但是当我需要某种结果或者想要在此任务结束后运行另一个任务时,我应该将其标记为“async/await”。当然,我可以使用“ContinueWith”手动编写它,但最好使用内置的功能。当然,我们不应忘记“ConfigureAwait”。 在提问之前,我阅读了一些有用的链接(以防万一):异步的禅TPL模式 - Alex Zhukovskiy
我还发现在这里处理异常时也应该使用 await。我的意思是,如果你只是从 WCF 服务返回一个任务(例如),并且它抛出了异常,你无法记录它,因为它在内部的 WCF 的 await 中失败了。但是当你在服务中等待它时,你可以使用未处理的异常处理程序来记录它。 - Alex Zhukovskiy

-2

不要只听我的话,因为我从来没有真正理解过async/await,但是最让我困扰的是所有使用async的方法也必须标记为async,这让我非常烦恼。

我想在一个库中给人们选择如何使用方法是一件好事,所以你应该使用async,但是我总觉得直接明确地使用Tasks更清晰。


实际上,这是一个不好的主意。无论方法是同步还是异步,使用任务(Tasks)与使用async/await没有什么区别,这些关键字只是对Tasks的简化。调用者将永远不会知道库函数被标记为async - Panagiotis Kanavos
除非您想使用await并且该方法未标记为async,否则它将无法工作 :) 我的建议是将方法标记为async,因为这将允许两种情况:等待或只是任务,就像我所做的那样。 - Siderite Zackwehdex
1
@SideriteZackwehdex - 但是在任何特定方法中使用async/await的决定是一个本地决策。它不会影响调用您的代码或您调用的代码。正如我在评论中所说,这是一个实现细节,您可以自由地向任何特定方法添加或删除它们。 - Damien_The_Unbeliever
当异步标记标记你正在调用的方法,而await在你编写的代码中时,你怎么能说它只影响调用方或被调用方呢?库需要实现异步方法才能启用await...或者我理解错了吗? - Siderite Zackwehdex
1
不,对于调用者来说,重要的是你的方法返回 SomeType 还是 Task<SomeType>。这个决定(返回类型)与 async/await 是完全分开的。确实,如果你采取大多数同步代码库并想将其变成异步,你经常会同时更改方法返回类型并使用 async/await 装饰函数,但一定要非常清楚,这些是不同的操作。 - Damien_The_Unbeliever

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接