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或Task对象。实际上,Task.FromResult创建了一个Status属性为RanToCompletion,且Result为0的一个对象。通过这种方式,我们可以将同步方法与异步方法统一起来进行调用,二者都可以使用await关键字,而FakeAsyncClass中两个方法的执行是同步的:
private 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