Search code examples
c#async-awaitlitedb

How to properly implement an interface that was designed for async usage?


Starting with the following (simplified) interface which has async/await in mind, I want to implement it by using LiteDB database.

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

Unfortunately, LiteDB currently does not support async methods. All methods are synchronous.

I tried the following, but later ran into some issues with that (process did not really wait for whatever reason). Following that, I read that you should only use Task.Run() for CPU bound algorithms, and therefore not for database access.

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

Even though I read a couple of blog articles and also SO questions, it is still unclear for me how to make a properly written awaitable method for synchronous IO based code.

So how can I write a properly awaitable method?

Note: I can't change the interface. This is given.


Solution

  • You must not use Task.Run. It is not a question of whether that is the morally correct thing to do. As you note, it is morally wrong to assign worker threads to tasks that are not CPU bound, but that's not the issue here. The issue here is that LiteDB is single-threaded and is not built to be robust in the face of attempting to move calls into it onto worker threads.

    UPDATE: Apparently LiteDB is thread safe in version 4, according to a commenter who is likely to be more informed than I am on this issue. So, consult LiteDB documentation for what kind of thread safe it is. Not all thread safe objects can be used without restriction on any thread.

    Your choices are:

    • Abandon use of this interface.
    • As the other answer suggests, lie. Say that your implementation is async when it is in fact synchronous. This can cause your user interface to hang, but hey, you were going to hang your user interface anyways when you made the synchronous long-running call. Explicitly calling FromResult is the right way to do that.
    • Get a better database that supports asynchrony. Or wait for LiteDB to be a better database, that supports asynchrony.
    • Move all of the calls to LiteDB, including the creation and destruction of every object associated with this database, onto a dedicated thread. (Probably in-process, but hey, you could put it in its own process if you like.) Implement your own asynchronous front end to LiteDB that marshals calls to and from this dedicated thread appropriately. Yes, you burn an entire thread that spends most of its time sleeping, but that's the price you pay for using a synchronous database.

    The solution that meets all your stated constraints is the last one. It's also the one that is the most work for you. As woodworkers like to say: what you save on cheap materials you'll spend on labour. Trying to retrofit a correct async interface onto a non-asynchronous but I/O bound single-threaded library is a tricky problem. Good luck!