5
votes

We are using Xamarin to write C# code with SQLite for android and ios. However about how to use sqlite, I seem to have a conceptual misunderstanding:

What are the best practices for SQLite on Android?

According to the stackoverflow answer it says - one helper and one db connection. Use lock around it to make sure only one thread is accessing sqlite db at any time.

My question is - if that is the case - what is the use for async?

I tried to use async with synchronized code - and the code gave me compile errors rightfully to avoid deadlocks.
Why can't I use the 'await' operator within the body of a lock statement?

    public async Task InsertAsync<T> (T item){
        lock (mutex) {
            await asyncConnection.InsertAsync (item);
        }
    }

    public async Task InsertOrUpdateAsync<T> (T item){
        lock (mutex) {
            int count = await asyncConnection.UpdateAsync (item);
            if (0 == count) {
                await asyncConnection.InsertAsync (item);
            }
        }
    }

fails. However - if I am going to use locks to ensure I am using one connection one thread at a time as a best practice in sqlite - why is there an async sqlite library at all?

And why are some threads promoting async sqlite usage on the web.

What is the real best practice for sqlite in android and iphone? Just using sync version?

2

2 Answers

7
votes

There's a big difference between synchronous vs asynchronous and single vs concurrent and there are all 4 combinations of them.

In the single asynchronous case you access the DB using a single thread at most, but it doesn't need to be the same thread throughout the operation, and when you don't need a thread (when you are waiting for the IO operation to complete) you don't need any threads at all.

The most basic way to limit the async usage to a single operation at a time is by using a SemaphoreSlim with initialCount = 1. A nicer way would be to use an AsyncLock (Building Async Coordination Primitives, Part 6: AsyncLock by Stephen Toub):

private readonly AsyncLock _lock = new AsyncLock(); 

public async Task InsertAsync<T> (T item)
{
    using(await _lock.LockAsync())
    {
        await asyncConnection.InsertAsync (item);
    }
}

public async Task InsertOrUpdateAsync<T> (T item)
{
    using(await _lock.LockAsync())
    {
        if (0 == await asyncConnection.UpdateAsync (item))
        {
            await asyncConnection.InsertAsync (item);
        }
    }
}

Note: My implementation of AsyncLock

4
votes

The fact that your Database doesn't accept multiple accesses (insertions, updates, etc..) doesn't mean that the single thread doing work against it has to do so using a blocking api.

If you don't have to do cross-process locking, you can use SemaphoreSlim.WaitAsync instead of your Mutex inside your async method to await the lock asynchrnously:

private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(initialCount: 1);

public async Task InsertAsync<T>(T item)
{
   await semaphoreSlim.WaitAsync();
   try
   {
      await asyncConnection.InsertAsync(item);
   }
   finally
   { 
      semaphoreSlim.Release();
   }
}

public async Task InsertOrUpdateAsync<T>(T item)
{
   await semaphoreSlim.WaitAsync();
   try
   {      
      int count = await asyncConnection.UpdateAsync(item);
      if (0 == count) 
      {
         await asyncConnection.InsertAsync(item);
      }
   }
   finally
   {
      semaphoreSlim.Release();
   }
}