当先前的实现是同步的时如何使用异步代码

How to use async code when previous implementation was synchronous

最近我在学习面向对象编程中的组合。在很多关于它的文章中,有一个 FileStore class(或者你怎么称呼它)的例子,它实现了如下接口:

interface IStore
{
   void Save(int id, string data);
   string Read(int id);
}

FileStore class 实现了这个接口,一切正常。它可以保存和读取文件。现在,教程文章经常说FileStore可以很容易地交换成,例如SqlStore,因为它可以实现相同的接口。这样客户端 classes 可以存储到 SQL 而不是文件系统,只需注入 SqlStore 而不是 FileStore。听起来不错,但当我真正想到它时,我真的不知道如何实现它。 数据库操作很适合异步完成。为了使用这个事实,我需要 return 一个 Task 而不是 Read(int id) 方法中的字符串。 Save(int id, string data) 也可以 return 一个任务。 考虑到这一点,我不能真正将 IStore 用于我的 SqlStore class.

我看到的一个解决方案是像这样制作 IStore:

interface IStore
{
   Task Save(int id, string data);
   Task<string> Read(int id);
}

然后,我的 FileStore class 需要稍微改变一下,它将使用 Task.FromResult(...).

这个解决方案对我来说似乎很不方便,因为 FileStore 必须假装一些异步特性。

您建议的解决方案是什么? 我很好奇,因为在教程中一切听起来总是简单易行,但是当涉及到实际编程时,事情就变得复杂了。

当我自己创建异步方法时,我总是只对可以在新线程中完成的操作执行此操作,并且这样做是安全的。

所以如果我知道我可以使 Save and/or Read 方法成为 FileStore class 运行 在单独的线程中没有问题我肯定会将 return 值更改为任务。

如果接口必须完好无损(如果它不是由您的项目创建的,或者其他代码依赖于它的同步方法),除了原始方法之外,还可以创建方法的异步版本,如下所示:

interface IStore
{
  void Save(int id, string data);
  Task SaveAsync(int id, string data);
  string Read(int id);
  Task<string> ReadAsync(int id);
}

(Microsoft 编码指南建议在任何异步方法的末尾使用后缀 Async。)

另一种方法可能是创建一个 IStoreAsync 异步版本接口,具体取决于您的需要。

在方法的异步版本中,他们现在可以创建一个调用同步方法的新线程,并等待线程完成,如下所示:

async Task<string> ReadAsync(int id) {
  string value = await Task.Run(() => Read(id));
  return value;
}

注意可能有很多更安全和更好的方法来创建新线程,但是Task.Run作为示例和测试目的。

我见过解决这个问题的不同方法,但选择的方法总是取决于你的情况。

  1. 将界面更改为基于 Task

如果您没有任何实现(或者它们可以很容易地更改)或者您不想保留任何同步版本,最简单的方法就是更改接口:

interface IStore
{
   Task SaveAsync(int id, string data);
   Task<string> ReadAsync(int id);
}
  1. 扩展界面

另一种选择是将异步版本的方法添加到接口中。如果存在同步的实现或者已经有太多实现,这可能不是最佳选择。

interface IStore
{
    void Save(int id, string data);
    Task SaveAsync(int id, string data);

    string Read(int id);
    Task<string> ReadAsync(int id);
}
  1. 通过继承创建异步版本

当您无法更改原始接口或并非所有实现都需要异步版本时,此选项最有意义。当然,您需要决定是要 IStore 还是 IAsyncStore

interface IStore
{
    void Save(int id, string data);
    string Read(int id);
}

interface IAsyncStore : IStore
{
    Task SaveAsync(int id, string data);
    Task<string> ReadAsync(int id);
}

注意:对于I/O操作的情况(FileStoreSqlStore都在你的例子中做),应该只有是异步方法。