当先前的实现是同步的时如何使用异步代码
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
作为示例和测试目的。
我见过解决这个问题的不同方法,但选择的方法总是取决于你的情况。
- 将界面更改为基于
Task
。
如果您没有任何实现(或者它们可以很容易地更改)或者您不想保留任何同步版本,最简单的方法就是更改接口:
interface IStore
{
Task SaveAsync(int id, string data);
Task<string> ReadAsync(int id);
}
- 扩展界面
另一种选择是将异步版本的方法添加到接口中。如果存在同步的实现或者已经有太多实现,这可能不是最佳选择。
interface IStore
{
void Save(int id, string data);
Task SaveAsync(int id, string data);
string Read(int id);
Task<string> ReadAsync(int id);
}
- 通过继承创建异步版本
当您无法更改原始接口或并非所有实现都需要异步版本时,此选项最有意义。当然,您需要决定是要 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操作的情况(FileStore
和SqlStore
都在你的例子中做),应该只有是异步方法。
最近我在学习面向对象编程中的组合。在很多关于它的文章中,有一个 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
作为示例和测试目的。
我见过解决这个问题的不同方法,但选择的方法总是取决于你的情况。
- 将界面更改为基于
Task
。
如果您没有任何实现(或者它们可以很容易地更改)或者您不想保留任何同步版本,最简单的方法就是更改接口:
interface IStore
{
Task SaveAsync(int id, string data);
Task<string> ReadAsync(int id);
}
- 扩展界面
另一种选择是将异步版本的方法添加到接口中。如果存在同步的实现或者已经有太多实现,这可能不是最佳选择。
interface IStore
{
void Save(int id, string data);
Task SaveAsync(int id, string data);
string Read(int id);
Task<string> ReadAsync(int id);
}
- 通过继承创建异步版本
当您无法更改原始接口或并非所有实现都需要异步版本时,此选项最有意义。当然,您需要决定是要 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操作的情况(FileStore
和SqlStore
都在你的例子中做),应该只有是异步方法。