如何正确实现专为异步使用而设计的接口?

时间:2018-10-29 17:46:47

标签: c# async-await litedb

从以下考虑了异步/等待的(简化)接口开始,我想使用LiteDB数据库来实现它。

public interface IDataService
{
    Task<User> GetUserAsync(int key);
}

不幸的是,LiteDB当前不支持异步方法。所有方法都是同步的。

我尝试了以下操作,但是后来遇到了一些问题(进程实际上并没有因为任何原因而等待)。之后,我读到您应该只将Task.Run()用于CPU约束算法,而不是数据库访问。

public Task<User> GetUserAsync(int key)
{
    var task = Task.Run(() =>
    {
        return _users
            .Find(x => x.Key == key)
            .SingleOrDefault();
    });
    return task;
}

即使我阅读了几篇博客文章以及SO问题,但我仍然不清楚如何为基于同步IO的代码编写正确编写的等待方法。

那么我该如何编写适当的等待方法?

注意:我无法更改界面。给出了。

2 个答案:

答案 0 :(得分:7)

不得使用Task.Run。这不是在道德上做正确的事的问题。正如您所注意到的,在道德上将工作线程分配给不受CPU约束的任务是错误的,但这不是这里的问题。这里的问题是LiteDB是单线程的,并且在尝试将对它的调用移动到工作线程时,并不是很健壮。

更新:据一位评论员说,显然LiteDB在版本4中是线程安全的,他可能比我更了解此问题。因此,请查阅LiteDB文档以了解什么是线程安全的种类。并非所有线程安全对象都可以不受任何线程限制地使用。

您的选择是:

  • 放弃使用此界面。
  • 正如另一个答案所暗示的那样,说谎。假设您的实现实际上是同步的,则它是异步的。这可能会导致用户界面挂起,但是,当您进行同步长时间运行的调用时,无论如何您都将挂起用户界面。明确调用FromResult是正确的方法。
  • 获取更好的支持异步的数据库。或者等待LiteDB成为支持异步的更好的数据库。
  • 所有调用移至一个专用线程上,包括对与此数据库关联的每个对象的创建和销毁。 (可能在进程内,但是,如果愿意,可以将其放在自己的进程中。)对LiteDB实现自己的异步前端,以适当地封送往返于该专用线程的调用。是的,您消耗了整个线程,其中大部分时间都在休眠,但这是您使用同步数据库所要付出的代价。

满足您所有所述限制的解决方案是最后一个。这也是最适合您的一项。就像木工说的那样:节省廉价材料后,您将在劳动力上花费。尝试将正确的异步接口改型为非异步但受I / O约束的单线程库是一个棘手的问题。祝你好运!

答案 1 :(得分:2)

一种选择是使用Task.FromResult并同步实现您的界面。

return Task.FromResult(_users.Find(x => x.Key == key).SingleOrDefault());

async/await的收获是使用I / O完成端口,该端口挂起线程并允许它处理其他传入请求,直到等待的I / O操作完成。看来您的实现无法利用此功能将您的工作包装在Task.Run或类似物中是没有好处的。