3

从下面考虑到 async/await 的(简化)接口开始,我想通过使用 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 的代码制作一个正确编写的可等待方法。

那么我怎样才能编写一个正确的等待方法呢?

注意:我无法更改界面。这是给的。

4

2 回答 2

8

不得使用Task.Run. 这不是道德上正确的做法的问题。正如您所指出的,将工作线程分配给不受 CPU 限制的任务在道德上是错误的,但这不是这里的问题。这里的问题是 LiteDB 是单线程的,并且在尝试将调用移到工作线程上时,它的构建并不健壮。

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

您的选择是:

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

满足您声明的所有约束的解决方案是最后一个。这也是最适合您的工作。正如木工喜欢说的:你在廉价材料上省下的钱,你会花在劳动力上。尝试将正确的异步接口改造成非异步但 I/O 绑定的单线程库是一个棘手的问题。祝你好运!

于 2018-10-29T18:04:14.177 回答
2

Task.FromResult一种选择是同步使用和实现您的接口。

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

回报async/await是使用 I/O 完成端口,它暂停线程并允许它为其他传入请求提供服务,直到等待的 I/O 操作完成。看到你的实现不能利用这个将你的工作包装在一个Task.Run或类似的文件中没有任何好处。

于 2018-10-29T17:49:20.863 回答