假设一个界面并考虑以下代码,它是我的合同
public interface ISomeContract
{
Task<int> SomeMethodAsync(CancellationToken cancellationToken);
int SomeMethod();
}
现在想象一下使用以下代码
合同实施ISomeContractpublic class SomeImplementation : ISomeContract
{
public int SomeMethod()
{
// lots of codes ...
return 10;
}
public async Task<int> SomeMethodAsync(CancellationToken cancellationToken)
{
return SomeMethod();
// another example: I can remove async modifier from SomeMethodAsync method and this following.
//try
//{
// return Task.FromResult<int>(SomeMethod());
//}
//catch (Exception ex)
//{
// return Task.FromException<int>(ex);
//}
}
}
如您所见,我在那里没有等待代码, 如果我没有真正的等待代码,我怎样才能重用SomeMethod主体呢? 甚至更多我在方法头中没有异步符号测试Task.FromResult,但是如何才能获得这个问题的最佳解决方案呢?
答案 0 :(得分:2)
我认为这里没有一个完美的答案:这在某种程度上取决于你想做什么。
首先,我们应该决定合同的消费者是否期望SomeMethodAsync()
快速返回,因为它是异步方法。在这种情况下,如果SomeMethod()
很慢,我们可能希望使用Task.Run()
来实现这一目标,即使这通常被视为不良做法:
// if you think that "returns quickly" is an important guarantee of SomeMethodAsync
public Task<int> SomeMethodAsync(CancellationToken cancellationToken)
{
return Task.Run(() => SomeMethod(), cancellationToken);
}
例如,您可以看到MSFT使用此default implementation of TextReader.ReadAsync方法。
如果SomeMethod()
速度相当快或者我们不关心快速返回(技术上异步方法无法保证),那么我们需要一些方法来模拟碰巧运行的异步方法的行为同步。特别是,故障/取消应导致故障/取消任务。一种方法是在显示时简单地使用异步方法:
public async Task<int> SomeMethodAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return SomeMethod();
}
这种方法的优点在于它非常简单且完全正确。另一个优点是某些原始任务结果由运行时缓存,而不是在使用Task.FromResult
时。例如,零任务被缓存:
public async Task<int> Z() => 0;
void Main()
{
Console.WriteLine(Task.FromResult(0) == Task.FromResult(0)); // false
Console.WriteLine(Z() == Z()); // true
}
因此,如果经常返回这些常用值,您可能会获得一些性能优势。
这样做的主要缺点是它会生成一个构建警告,因为你有一个没有等待的异步方法。您可以使用#pragma
来抑制它,但这会使代码变得丑陋并且仍然可能会混淆其他开发人员阅读代码。另一个轻微的缺点是,在某些情况下,这可能比手动构建任务的性能稍差。例如,在取消传入令牌的情况下,我们必须通过抛出异常来进行通信。
这导致我们选择了第二个选项,稍加调整以正确处理取消:
public Task<int> SomeMethodAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}
try { return Task.FromResult(SomeMethod()); }
catch (OperationCanceledException oce)
{
var canceledTaskBuilder = new TaskCompletionSource<int>();
canceledTaskBuilder.SetCanceled();
return canceledTaskBuilder.Task;
}
catch (Exception e)
{
return Task.FromException<int>(e);
}
}
这非常笨重,所以大部分时间我都会使用前两个选项中的一个,或者编写一个辅助方法来包装第三个选项中的代码。
答案 1 :(得分:1)
我想补充一点,如果您在SomeMethod
中执行任何IO,则不希望实际重复使用它(使用Task.Run
或任何其他方法)。相反,您希望使用异步IO重写它,因为使用异步的一个要点是利用异步IO。例如,假设你有这个:
public long SomeMethod(string url)
{
var request = (HttpWebRequest)WebRequest.Create(url);
var response = request.GetResponse();
return response.ContentLength;
}
在这种情况下,您不希望在SomeMethodAsync
中重复使用此方法,因为您执行request.GetResponse()
,即IO,并且它具有异步版本。所以你必须这样做:
public async Task<long> SomeMethodAsync(string url, CancellationToken cancellationToken) {
var request = (HttpWebRequest) WebRequest.Create(url);
using (cancellationToken.Register(() => request.Abort(), false)) {
try {
var response = await request.GetResponseAsync();
return response.ContentLength;
}
catch (WebException ex) {
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException(ex.Message, ex, cancellationToken);
throw;
}
}
}
正如您所看到的那样,在这种情况下它会更长一些(因为GetResponseAsync
不接受取消令牌),但如果您使用任何IO,这将是正确的方法。