SushiHangover.RealmThread
An Action/Task Message Pump for running commands on a dedicated Realm thread.
|
An Action/Task Message Pump for running commands on a dedicated Realm thread.
https://github.com/sushihangover/RealmThread
https://sushihangover.github.io/RealmThread/
Install-Package SushiHangover.RealmThread
Nuget.org: https://www.nuget.org/packages/sushihangover.realmthread/
Post an Issue on Github
Instance a new RealmThread
by passing a RealmConfiguration
, either from an existing Realm
instance (Realm.Config
) or constructing one yourself.
var realmConfig = new RealmConfiguration("myRealm.db"); var realmThread = new RealmThread(realmConfig);
RealmThread
implements IDisposable
and to ensure data integrity call Dispose
to ensure that all queued work on the dedicated Realm thread is completed.
using (var realmThread = new RealmThread(realmConfig)) { // Perform some work with the RealmThread }
Or:
var realmThread = new RealmThread(realmConfig); // At some future point: realmThread.Dispose();
InvokeAsync
: Tasks and ContinuationsTo ensure that Task
continuations are performed on the same thread, use InvokeAsync
when your Task contains other awaited Tasks, otherwise use Invoke
or BeginInvoke
to execute an Action
.
Note: This is a blocking call and will not return till all the queued items up till, including this one along with its continuations are executed.
await realmThread.InvokeAsync(async r => { await Task.FromResult(true); // Simulate some Task, i.e. a httpclient request.... // The following continuations will be executed on the proper thread r.Write(() => { var obj = r.CreateObject<KeyValueRecord>(); obj.Key = "key"; }); });
BeginInvoke
: A Non-blocking Action:BeginInvoke
will queue an Action
and immediately return (Fire & Forget
).
Note: The items in the work queue are executed in the order they were queued (FIFO) and to mantain data integrity they can not be reordered once added.
realmThread.BeginInvoke(internalRealmOnThread => { internalRealmOnThread.Write(() => { var obj = internalRealmOnThread.CreateObject<KeyValueRecord>(); obj.Key = "key"; }); });
Invoke
: A Blocking Action:Invoke
will queue an Action
and wait till it is executed.
Note: The items in the work queue are executed in the order they were added, so until all the queued items up till and including this one are executed, this call will block.
realmThread.Invoke(internalRealmOnThread => { internalRealmOnThread.Write(() => { var obj = internalRealmOnThread.CreateObject<KeyValueRecord>(); obj.Key = "key"; }); });
RealmThread
has a built-in Realms.Transaction
and a matching set of APIs that can be used instead of using a Realms.Transaction
via a captured variable:
BeginTransaction
CommitTransaction
RollbackTransaction
InTransaction
realmThread.BeginTransaction(); realmThread.Invoke(r => { var obj = r.CreateObject<KeyValueRecord>(); obj.Key = "key"; }); if (realmThread.InTransaction) { realmThread.CommitTransaction(); }
RealmThread
object is disposed, if the built-in transaction is open, it will perform a **Rollback
**. If you wish to override this behavoir, when creating a RealmThread
, set the autoCommit
parameter in the constructor to true
.You can use captured variables within your RealmThread
-based Action
and Task
to pass variables between the calling thread and the internal thread that RealmThread
maintains a Realm
instance on.
You can not pass a managed RealmObject
or RealmResult
in this manner as its access has to be made on the same thread it was instanced.
If needed, you could copy a managed RealmObject
to a non-managed RealmObject
and use this non-managed RealmObject
variable as your captured variable that is passed between threads.
var keyValueRecord = new KeyValueRecord(); realmThread.Invoke(r => { var obj = r.ObjectForPrimaryKey<KeyValueRecord>(keyToFind); keyValueRecord.Key = obj.Key; keyValueRecord.Value = obj.Value; }); Console.WriteLine($"{keyValueRecord.Key}:{keyValueRecord.Value}");
Note: This was the primary way to control a Transaction
over multiple invokes before the Transaction helper api was added.
Transaction trans = null; realmThread.Invoke(r => { trans = r.BeginWrite(); }); // Start a bunch of RealmObject inserts/edits realmThread.Invoke(r => { var obj = r.CreateObject<KeyValueRecord>(); obj.Key = "key"; }); // Complete the transaction t.Invoke(r => { if (trans != null) trans.Commit(); });
While this is a contrived example, it shows one RealmThread
being used to only add records and another being used to read these new records.
Note: The toWrite
variable is a pre-populated Dictionary<string, byte[]>
, see the performace tests in the source repo, within the RealmThread.Tests.Shared
project, for creation details...
using (var blockingQueue = new BlockingCollection<string>()) using (var realmThreadWrite = new RealmThread(aRealmInstance.Config)) using (var realmThreadRead = new RealmThread(aRealmInstance.Config)) { Parallel.Invoke(() => { realmThreadWrite.Invoke(threadSafeWriteRealm => { foreach (var kvp in toWrite) { // Individual record write transactions so the other RealmThread can read asap threadSafeWriteRealm.Write(() => { var obj = threadSafeWriteRealm.CreateObject(typeof(KeyValueRecord).Name); obj.Key = kvp.Key; obj.Value = kvp.Value; }); blockingQueue.Add(kvp.Key); } blockingQueue.CompleteAdding(); }); }, () => { realmThreadRead.Invoke((threadSafeReadRealm) => { foreach (var key in blockingQueue.GetConsumingEnumerable()) { // Refresh() is automatically called at the beginning of each BeginInvoke/Invoke, // so if we are within the RealmPump block and need to see the latest changes // from other Realm instances, call Refresh manually threadSafeReadRealm.Refresh(); var record = threadSafeReadRealm.ObjectForPrimaryKey<KeyValueRecord>(key); Assert.NotNull(record); Assert.Equal(key, record.Key); } }); }); }
From the cmd line using the amazing cake:
API Reference documention is built via the great
export NUGET_APIKEY={APIKEY} export GITHUB_TOKEN={TOKEN/PASSWORD} export GITHUB_USERNAME={EMAILADDRESS} export NUGET_SOURCE=https://www.nuget.org/api/v2/package ./build.sh -t PublishPackages
<head> <style> .nuget-badge code { -moz-border-radius: 5px; -webkit-border-radius: 5px; background-color: #202020; border: 4px solid silver; border-radius: 5px; box-shadow: 2px 2px 3px #6e6e6e; color: #e2e2e2; display: block; font: 1.0em 'andale mono', 'lucida console', monospace; line-height: 1.5em; overflow: auto; padding: 15px } .nuget-badge code::before { content: "PM> " } .code { -moz-border-radius: 5px; -webkit-border-radius: 5px; background-color: #202020; border: 4px solid silver; border-radius: 5px; box-shadow: 2px 2px 3px #6e6e6e; color: #e2e2e2; display: block; font: 1.0em 'andale mono', 'lucida console', monospace; line-height: 1.5em; overflow: auto; padding: 15px }
</style> </head>