Reputation: 422
I'm trying to better understand how to use Realm in an Async scenario. As far as I'm aware a Realm instance can only access objects on the thread it was created. So to do an async transaction I need a new instance of the Realm using GetInstance("SomeRealmFile");
.
There is a function called Task WriteAsync(Action<Realm> action)
which can execute a write transaction on a worker (background) thread.
Here's an example scenario. The app needs to make a web request. So it runs an async Task function to go off to the web and fetch some data. The request returns successfully and now the app wants to take that data parse it in to a Realm object and persist it to the database using Realm.Manage<T>(T Obj)
. Just a note the entire scenario assumes we have created the Realm database on the UI Main thread to begin with.
Let's assume we want to stay on the background thread at this point, in the context of the async Task function that started the web request.
So now we're in the situation where we have the results of the web request (data) via var results = await WebFetch();
, possibly using something like TaskCompletionSource instead and then finally we can do the Realm write transactions on a worker thread.
I assume to call the async write transaction at this point we would need to get a new instance of the Realm and pass it to the WriteAsync()
function.
This raises another question, let's assume before the database write transactions we want to do some queries and check a few things before we write stuff. What happens if we do these queries in an async function? Because I've run in to a lot of weirdness trying this before, sometimes it works and sometimes the queries just return null (depending on the type of query of course). Queries that work faultlessly on the main thread.
Edit This could be a bug, queries in an async function have been very unreliable.
So for clarity, we're still on the assumption that we have previously returned from an async Task (web fetch) and now we're doing a few Realm queries and then calling the Task WriteAsync(Action<Realm> action)
function to write the data to the database. Is this the correct approach? Some clarity on this one would be great.
One final point, are async write transactions even necessary? What sort of Realm scenario (write transaction) might block the UI thread for long enough that it would require and justify to be executed on the worker thread (from a mobile development perspective only).
Edit This was answered by the async example linked below, any process that takes up a lot of cpu resources. So one would assume basic Realm Write transactions, for example managing a list of 50 objects shouldn't need to be async?
A pseudo code sample would be great.
Edit Added a code sample off the top of my head.
async Task RealmWritesAsync
{
var result = await WebFetch();
// Maybe do some queries here
var realm = Realm.GetInstance("RealmFile");
await realm.WriteAsync(realm =>
{
// Realm.Manage the results
});
}
Environment = Xamarin.iOS/Droid project with a PCL using MvvmCross
Upvotes: 1
Views: 2941
Reputation: 1547
I encountered this in my own app recently and never found a good answer on StackOverflow, so I'll share what I learned.
The WriteAsync
function on the Realm
class is only for if you need to do a lot of CPU-heavy work inside a write transaction. The Realm
instance passed to the lambda is an instance on the worker thread which must be used instead of the original Realm
instance for operations done inside that lambda.
If you just want to do asynchronous work inside a write transaction that's not necessarily CPU-heavy, you'll have to manually start a transaction and then commit it once you're done. I wrote an extension method to make this easier, named WriteTask
to avoid a conflict with the existing WriteAsync
function:
/// <summary>
/// Async-friendly version of Realm.Write, meaning the work function can return a task
/// which will be awaited in the scope of the transaction.
/// </summary>
public static async Task WriteTask(this Realm realm, Func<Task> workFunction)
{
using var transaction = realm.BeginWrite();
await workFunction();
transaction.Commit();
}
Note that this uses the C# 8.0 using declaration syntax. If you can't use C# 8.0, simply replace it with a using block:
using (var transaction = realm.BeginWrite())
{
await workFunction();
transaction.Commit();
}
As long as the .WriteTask
method is invoked from the main (UI) thread, and .ConfigureAwait(false)
is not used when awaiting any Task
s inside the lambda, this will allow async work to be done inside a write transaction without needing a separate Realm
instance. Take care not to have multiple concurrent asynchronous operations trying to start transactions at the same time, however, as this will throw an exception.
Upvotes: 1