当接口实现不提供异步操作时该怎么办?

10

假设我有一个接口:

interface ILoader()
{
    Task<Data> LoadAsync(int id);
}

我有两个此接口的实现:

class NiceDataBase : ILoader 
{
    public async Task<Data> LoadAsync(int id)
    {
        //this database has async way of fetching the data
        return await NiceDataBaseDriver.LoadDataAsync(id);
    }
}

class NotNiceDataBase : ILoader 
{
    public async Task<Data> LoadAsync(int id)
    {
        //this database's driver does not have aysnc methods, because it's old or done poorly.
        return await Task.Run(() => NotNiceDataBaseDriver.LoadData(id));
    }
}

NotNiceDataBase没有提供真正的异步加载数据的方式,所以我实际上在新线程中运行它,但我理解的是,这不是真正的异步,因为真正的异步不使用新线程。那么我的NotNiceDataBase实现是好的吗?它有点欺骗用户认为他正在运行一个真正的异步操作。

如何处理这种情况是最优的?我认为NotNiceDataBase的客户端应该完全知道自己在做什么,否则他将无法很好地控制应用程序的性能。

我的界面可以有额外的Load方法。但在这种情况下,真正的好处是什么?仍然需要一些NotNiceDataBase的LoadAsync实现。我认为抛出NotImplementedException永远都不是一个好的解决方案。


1
我可能会向 NotNiceDatabase 实现添加注释或修改接口以提供非异步的 Data Load(int id) - IronAces
1
我不会回答,因为异步操作的复杂性并不是我的专长。但是如果你想保持实现异步接口,可以使用 await Task.Yield(否则按照 Daniel 提到的方法进行)。或者你可以使用 Task.FromResult 并使方法不是异步的,但我的理解是这两个选项都有其缺陷。 - Dave
1
那违背了接口的初衷。使用NiceDataBase的消费者正在使用异步方法,如果他们开始使用NotNiceDataBase,他们将期望它能够正常工作。显然,你不能指望他们去改变自己的代码以开始使用Data Load(int id)方法。 - Nisarg Shah
1
请注意,如果您坚持使用此实现,可以从NotNiceDatabase中的LoadAsync中删除asyncawait合同要求您返回一个Task<Data>。您的Task.Run调用显然正在生成这样的项目。这里不需要添加异步机制。 - Damien_The_Unbeliever
1
@Loreno - 正是我所说的。在 NotNiceDatabase 中,LoadAsync 不需要 asyncawait,而客户端的可观察行为将会(大多数情况下)相同。那么为什么要强制编译器为该方法构建异步状态机呢?当它不需要时。 - Damien_The_Unbeliever
显示剩余3条评论
2个回答

6

正如你所知,async是一种实现细节,你不能在接口中定义 async。所以你所要做的就是

public class NotNiceDataBase : ILoader
{
    public Task<Data> LoadAsync(int id)
    {
        //this database's driver does not have aysnc methods, because it's old or done poorly.    
        var result = NotNiceDataBaseDriver.LoadData(id);
        return Task.FromResult(result);
     }
}

@pere57:有什么替代方案吗?你可以编写一个IInfoFacade接口,其中包含一个GetAsync方法。你可以为MongoDb、SQL、Oracle等提供实现……3年后,你的客户必须与一个没有异步方法的奇怪过时的存储库集成。在整个代码库中广泛使用了IInfoFacade,你无法重构以添加同步方法(太多的实现将会破坏),也不能添加新接口。在这种情况下,你该怎么办? - Binary Worrier
1
@EmrahSüngü 这个解决方案更加“作弊” - 它完全以同步的方式运行,它会阻塞线程,而方法签名让你觉得它不会阻塞。 - Loreno
2
@Loreno,我只是模仿微软实现的方式。如果微软没问题,那我也没问题。 - Hasan Emrah Süngü
@pere57 定义足够快。顺便问一下,为什么更长的路径是异步的?它只是从 Task.Run 返回一个任务。 - Hasan Emrah Süngü
@pere57,Task.FromResult不是立即返回吗?顺便说一下,很抱歉我无法发布任何asp.net core参考资料,因为我正在使用手机,使用浏览器太麻烦了 :) - Hasan Emrah Süngü
显示剩余2条评论

5

只需同步运行该方法并返回已完成的任务,例如:

class NotNiceDataBase : ILoader 
{
    public Task<Data> LoadAsync(int id)
    {
        var data = NotNiceDataBaseDriver.LoadData(id);
        return Task.From(data);
    }
}

您说:

我认为,NotNiceDataBase的客户端完全应该了解自己在做什么,否则就无法很好地控制他的应用程序的性能。

我不同意,如果性能是一个问题并且如果他们已经确定这是对NotNiceDataBase的调用,那么他们可以采取一些措施。

解释

花费时间担心客户端如何使用接口 几乎肯定1是浪费时间,甚至不是“过早优化”,而是对他人未知优化需求的存在焦虑。

1除非您自己是客户,否则这仍然是可能浪费时间。


3
这个答案与@EmrahSüngü的答案有何不同?除了不能编译,因为不存在名为Task.From的方法之外,没有其他区别。 - FCin
1
@FCin 我相信我们大约在同一时间回答了这个问题 (;^_^A - Hasan Emrah Süngü
请问您能详细说明一下“如果性能是一个问题,并且他们已经确定了调用NotNiceDataBase是原因,那么他们可以采取措施解决”这句话吗?我想了解更多信息。 - Hasan Emrah Süngü
@FCin:是的,Emrah Süngü和我几乎同时发布了帖子。 - Binary Worrier
1
担心客户如何使用接口的时间几乎肯定是浪费时间,而且不仅仅是“过早优化”,而是对他人未知的优化需求的存在焦虑。尤其是当您开发具有一些SLA考虑的产品时,这并不总是正确的。无论如何,即使没有这个考虑,让客户知道发生了什么也是一种礼貌。如果我使用具有异步合同的库,并且结果表明它们实际上是同步的,但只是包装成异步外壳,这会影响我的结果,我个人会感到不愉快的惊讶。 - Dmytro Mukalov
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接