C#异步接口的同步实现
实际工程中有时需要将同步方法包装在异步方法之中。比如一个类实现了异步接口,而实际实现可能是本地计算的同步操作,也可能是需要HTTP请求的异步操作。对于前者,如果实现中没有await关键字,编译器会提示该方法缺少await运算符,将以同步方式运行。将二者统一的解决方案是使用Task.FromResult将结果包装成一个已完成的Task返回。举例:
首先定义了一个异步接口IWork,不失普遍性,它包括了有返回值和无返回值的两个方法:
public interface IWork
{
Task WorkAsync();
Task<int> WorkWithResultAsync();
}
既然定义为异步接口,那么实现此接口的AsyncClass如下,其中延时500ms为模拟的CPU或IO耗时异步操作:
public class AsyncClass : IWork
{
public async Task WorkAsync()
{
await Task.Delay(500); // Mock CPU intensive operations
Console.WriteLine("RealAsync WorkAsync Done");
}
public async Task<int> WorkWithResultAsync()
{
await Task.Delay(500); // Mock CPU intensive operations
Console.WriteLine("RealAsync WorkWithResultAsync Done");
return 0;
}
}
但我们还需要有另一个实现此接口的类FakeAsyncClass,它仅需要简单的本地计算,并无await运算符:
public class FakeAsyncClass : IWork
{
public async Task WorkAsync()
{
Console.WriteLine("FakeAsync WorkAsync Done");
}
public async Task<int> WorkWithResultAsync()
{
Console.WriteLine("FakeAsync WorkWithResultAsync Done");
return 0;
}
}
此时编译器会在WorkAsync()和WorkWithResultAsync()两个方法下提示它们会以同步方式运行,这当然不是合理的做法。因此,我们使用Task.FromResult将其改进如下:
public class FakeAsyncClass : IWork
{
public Task WorkAsync()
{
Console.WriteLine("FakeAsync WorkAsync Done");
return Task.CompletedTask; // Task.FromResult<object>(null) if < .NET 4.6
}
public Task<int> WorkWithResultAsync()
{
Console.WriteLine("FakeAsync WorkWithResultAsync Done");
return Task.FromResult(0);
}
}
注意,除了使用Task.FromResult改变了返回值外,我们还将这两个方法的async关键字去掉了,即它们并非异步方法,仅返回一个Task或Taskprivate static async Task SyncToAsync()
{
var workList = new List<IWork>
{
new AsyncClass(),
new FakeAsyncClass()
};
foreach (var work in workList)
{
await work.WorkAsync();
await work.WorkWithResultAsync();
}
}
References
How to: Create Pre-Computed Tasks Task.FromResult(TResult) Method Task.CompletedTask Property