如何使用异步方法包装遗留同步 I/O 绑定操作?

How to wrap a legacy synchronous I/O bound operation with an asynchronous method?

我仍在学习 async/await 概念,请耐心等待。假设有这个遗留代码定义了从数据源(实际情况下是本地数据库)获取数据的合同:

public interface IRepository<T> {
    IList<T> GetAll();
}

在新代码中,我有以下界面:

public interface IMyObjectRepository {
    Task<IEnumerable<MyObject>> GetAllAsync();
}

MyObjectRepository class 依赖于 IRepository<T> 是这样的:

public class MyObjectRepository : IMyObjectRepository
{
    private readonly IRepository<Some.Other.Namespace.MyObject> repo_;

    private readonly IDataMapper<Some.Other.Namespace.MyObject, MyObject> mapper_;

    public BannerRepository(IRepository<Some.Other.Namespace.MyObject> repo, IDataMapper<Some.Other.Namespace.MyObject, MyObject> mapper)
    {
        repo_ = repo;
        mapper_ = mapper;
    }
}

如何以在异步编程上下文中有意义的方式实现 IMyObjectRepository.GetAllAsync() 方法?从一些本地数据源获取数据是一个 I/O 绑定操作,所以我这样做的方式是:

public async Task<IEnumerable<MyObject>> GetAllAsync()
{
    var tcs = new TaskCompletionSource<IEnumerable<MyObject>>();

    try
    {
        GetAll(objects =>
        {
            var result = new List<MyObject>();

            foreach (var o in objects)
            {
                result.Add(mapper_.Map(o));
            }

            tcs.SetResult(result);
        });
    }
    catch (Exception e)
    {
        tcs.SetException(e);
    }

    return await tcs.Task;
}

private void GetAll(Action<IEnumerable<Some.Other.Namespace.MyObject>> handle)
{
    IEnumerable<Some.Other.Namespace.MyObject> objects = repo_.GetAll<Some.Other.Namespace.MyObject>();

    if (objects is null)
    {
        objects = Enumerable.Empty<Some.Other.Namespace.MyObject>();
    }

    handle(objects);
}

这有意义吗?我不想使用 Task.Run(),因为据我了解,在 I/O 绑定操作的情况下,这会白白浪费一个线程。

示例中的方法是有效的。然而,我会小心只在绝对需要时才使用它,因为用户在调用“异步”方法时可能会非常困惑,只是为了阻止它。从这个意义上讲,使用 Task.Run() 可能更可取,因为那样不会阻塞。但它确实要求调用的代码是线程安全的。

在您的示例中,使用 async 没有任何意义,只需删除它,await 应该会给出完全相同的结果,因为您无论如何都会返回一个任务。

使用 Task.FromResult 是另一种简单的选择。但如果出现异常,则不会完全相同。使用 TaskCompletionSource.SetException 将使异常作为 Task.Exception 的 属性 而不是从方法调用本身引发。如果等待任务,则无关紧要,因为这两种情况都应由 try/catch 处理。但是还有其他方法可以处理任务的异常,因此您需要知道它是如何使用的,并提供适当的 documentation/comments 来描述行为。

不要用异步实现来实现同步方法。也不要实现伪异步方法(一种仅在线程池线程上运行同步代码的异步方法)。相反,先退后一步...

How do I implement the IMyObjectRepository.GetAllAsync() method in such a way that it makes sense in the context of asynchronous programming?

错误的问题。

不要通过说“我如何使这个异步”来接近异步,而是从另一端接近它。从 应该 异步的事情开始。如您所述,I/O 非常适合异步代码。因此,从实际 执行 I/O 的代码开始 - 最低级别的方法。这可能是 IRepository<T>.GetAll - 实际与数据库进行通信的任何内容。将 that 更改为使用 await 调用异步等效项,并允许 async 从最低级别的方法扩展到代码的其余部分。