Skip to content

Commit

Permalink
[v11] 3. Remove obsolete WriteAsync overloads (#3234)
Browse files Browse the repository at this point in the history
  • Loading branch information
nirinchev authored Feb 24, 2023
1 parent 829315b commit a07621c
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 541 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@
* `RealmObjectBase.DynamicApi.CreateObject(string)` can be used to create an object without a primary key.
* `RealmObjectBase.DynamicApi.CreateObject(string, string/long?/ObjectId?/Guid?)` can be used to create an object with a primary key of the corresponding type.
* The API exposed by `Realm.DynamicApi` no longer return `dynamic`, instead opting to return concrete types, such as `IRealmObject`, `IEmbeddedObject`, and so on. You can still cast the returned objects to `dynamic` and go through the dynamic API, but that's generally less performant than using the string-based API, such as `IRealmObjectBase.DynamicApi.Get/Set`, especially on AOT platforms such as iOS or Unity. (Issue [#2391](https://github.com/realm/realm-dotnet/issues/2391))
* Removed `Realm.WriteAsync(Action<Realm>)` in favor of `Realm.WriteAsync(Action)`. The new `WriteAsync` method introduced in 10.14.0 is more efficient and doesn't require reopening the Realm on a background thread. While not recommended, if you prefer to get the old behavior, you can write an extension method like:
```csharp
public static async Task WriteAsync(this Realm realm, Action<Realm> writeAction)
{
await Task.Run(() =>
{
using var bgRealm = Realm.GetInstance(realm.Config);
bgRealm.Write(() =>
{
writeAction(bgRealm);
});
});

await realm.RefreshAsync();
}
```

### Enhancements

Expand Down
257 changes: 0 additions & 257 deletions Realm/Realm/Realm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -887,54 +887,6 @@ public async Task<Transaction> BeginWriteAsync(CancellationToken cancellationTok
return _activeTransaction = new Transaction(this);
}

/// <summary>
/// Execute an action inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="action"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// await realm.WriteAsync(tempRealm =&gt;
/// {
/// var pongo = tempRealm.All&lt;Dog&gt;().Single(d =&gt; d.Name == "Pongo");
/// var missis = tempRealm.All&lt;Dog&gt;().Single(d =&gt; d.Name == "Missis");
/// for (var i = 0; i &lt; 15; i++)
/// {
/// tempRealm.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// Mum = missis,
/// Dad = pongo
/// });
/// }
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="action">
/// Action to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <returns>An awaitable <see cref="Task"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Action action) instead.")]
public Task WriteAsync(Action<Realm> action)
{
ThrowIfDisposed();

Argument.NotNull(action, nameof(action));

return WriteAsync(tempRealm =>
{
action(tempRealm);
return true;
});
}

/// <summary>
/// Execute an action inside a temporary <see cref="Transaction"/>. If no exception is thrown, the <see cref="Transaction"/> will be committed.
/// <b>If</b> the method is not called from a thread with a <see cref="SynchronizationContext"/> (like the UI thread), it behaves synchronously.
Expand Down Expand Up @@ -970,70 +922,6 @@ public Task WriteAsync(Action action, CancellationToken cancellationToken = defa
}, cancellationToken);
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="function"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// var dog = await realm.WriteAsync(tempRealm =&gt;
/// {
/// return tempRealm.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// });
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="function">
/// Delegate with one return value to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <typeparam name="T">The type returned by the input delegate.</typeparam>
/// <returns>An awaitable <see cref="Task"/> with return type <typeparamref name="T"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Func<T> function) instead.")]
public async Task<T> WriteAsync<T>(Func<Realm, T> function)
{
ThrowIfDisposed();

Argument.NotNull(function, nameof(function));

// If running on background thread, execute synchronously.
if (!AsyncHelper.TryGetValidContext(out _))
{
return Write(() => function(this));
}

// If we are on UI thread the SynchronizationContext will be set (often also set on long-lived workers to use Post back to UI thread).
var result = await Task.Run(() =>
{
using var realm = GetInstance(Config);
var writeAction = realm.Write(() => function(realm));
if (writeAction is IRealmObjectBase rob && rob.IsManaged && rob.IsValid)
{
return (object)ThreadSafeReference.Create(rob);
}

return writeAction;
});

await RefreshAsync();

if (result is ThreadSafeReference.Object<IRealmObjectBase> tsr)
{
return (T)(object)ResolveReference(tsr);
}

return (T)result;
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/>. If no exception is thrown, the <see cref="Transaction"/> will be committed.
/// <b>If</b> the method is not called from a thread with a <see cref="SynchronizationContext"/> (like the UI thread), it behaves synchronously.
Expand Down Expand Up @@ -1080,151 +968,6 @@ public async Task<T> WriteAsync<T>(Func<T> function, CancellationToken cancellat
return result;
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="function"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// var dogs = await realm.WriteAsync(tempRealm =&gt;
/// {
/// tempRealm.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// });
///
/// tempRealm.Add(new Dog
/// {
/// Breed = "Poddle",
/// });
///
/// return tempRealm.All&lt;Dog&gt;();
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="function">
/// Delegate with return type <see cref="IQueryable{T}"/> to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <typeparam name="T">The type of data in the <see cref="IQueryable{T}"/>.</typeparam>
/// <returns>An awaitable <see cref="Task"/> with return type <see cref="IQueryable{T}"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Func<T> function) instead.")]
public async Task<IQueryable<T>> WriteAsync<T>(Func<Realm, IQueryable<T>> function)
where T : IRealmObjectBase
{
ThrowIfDisposed();

Argument.NotNull(function, nameof(function));

// If running on background thread, execute synchronously.
if (!AsyncHelper.TryGetValidContext(out _))
{
return Write(() => function(this));
}

// If we are on UI thread the SynchronizationContext will be set (often also set on long-lived workers to use Post back to UI thread).
var result = await Task.Run(() =>
{
using var realm = GetInstance(Config);
var writeResult = realm.Write(() => function(realm));
if (writeResult is RealmResults<T> rr && rr.IsValid && rr.IsManaged)
{
return (object)ThreadSafeReference.Create(writeResult);
}

return writeResult;
});

await RefreshAsync();

if (result is ThreadSafeReference.Query<T> tsr)
{
return ResolveReference(tsr);
}

return (IQueryable<T>)result;
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="function"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// var markDogs = await realm.WriteAsync(tempRealm =&gt;
/// {
/// var mark = tempRealm.All&lt;Person&gt;().Single(d =&gt; d.Name == "Mark");
///
/// mark.Dogs.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// });
///
/// mark.Dogs.Add(new Dog
/// {
/// Breed = "Poodle",
/// });
///
/// return mark.Dogs;
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="function">
/// Delegate with return type <see cref="IList{T}"/> to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <typeparam name="T">The type of data in the <see cref="IList{T}"/>.</typeparam>
/// <returns>An awaitable <see cref="Task"/> with return type <see cref="IList{T}"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Func<T> function) instead.")]
public async Task<IList<T>> WriteAsync<T>(Func<Realm, IList<T>> function)
{
ThrowIfDisposed();

Argument.NotNull(function, nameof(function));

// If running on background thread, execute synchronously.
if (!AsyncHelper.TryGetValidContext(out _))
{
return Write(() => function(this));
}

// If we are on UI thread the SynchronizationContext will be set (often also set on long-lived workers to use Post back to UI thread).
var result = await Task.Run(() =>
{
using var realm = GetInstance(Config);
var writeResult = realm.Write(() => function(realm));
if (writeResult is RealmList<T> rl && rl.IsValid && rl.IsManaged)
{
return (object)ThreadSafeReference.Create(writeResult);
}

return writeResult;
});

await RefreshAsync();

if (result is ThreadSafeReference.List<T> tsr)
{
return ResolveReference(tsr);
}

return (IList<T>)result;
}

/// <summary>
/// Update the <see cref="Realm"/> instance and outstanding objects to point to the most recent persisted version.
/// </summary>
Expand Down
81 changes: 0 additions & 81 deletions Tests/Realm.Tests/Database/AsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,87 +30,6 @@ namespace Realms.Tests.Database
[TestFixture, Preserve(AllMembers = true)]
public class AsyncTests : RealmInstanceTest
{
[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_ShouldExecuteOnWorkerThread()
{
TestHelpers.RunAsyncTest(async () =>
{
var currentThreadId = Environment.CurrentManagedThreadId;
var otherThreadId = currentThreadId;

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(0));
Assert.That(SynchronizationContext.Current != null);
await _realm.WriteAsync(realm =>
{
otherThreadId = Environment.CurrentManagedThreadId;
realm.Add(new Person());
});

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(1));
Assert.That(otherThreadId, Is.Not.EqualTo(currentThreadId));
});
}

[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_WhenOnBackgroundThread_ShouldExecuteOnSameThread()
{
TestHelpers.RunAsyncTest(async () =>
{
await Task.Run(async () =>
{
var currentThreadId = Environment.CurrentManagedThreadId;
var otherThreadId = -1;

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(0));
Assert.That(SynchronizationContext.Current == null);

await _realm.WriteAsync(realm =>
{
otherThreadId = Environment.CurrentManagedThreadId;
realm.Add(new Person());
});

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(1));
Assert.That(otherThreadId, Is.EqualTo(currentThreadId));

_realm.Dispose();
});
});
}

[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_UpdateViaPrimaryKey()
{
TestHelpers.RunAsyncTest(async () =>
{
IntPrimaryKeyWithValueObject obj = null;
_realm.Write(() =>
{
obj = _realm.Add(new IntPrimaryKeyWithValueObject { Id = 123 });
});

await _realm.WriteAsync(realm =>
{
var dataObj = realm.Find<IntPrimaryKeyWithValueObject>(123);
dataObj.StringValue = "foobar";
});

// Make sure the changes are immediately visible on the caller thread
Assert.That(obj.StringValue, Is.EqualTo("foobar"));
});
}

[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_ShouldRethrowExceptions()
{
TestHelpers.RunAsyncTest(async () =>
{
const string message = "this is an exception from user code";
var ex = await TestHelpers.AssertThrows<Exception>(() => _realm.WriteAsync(_ => throw new Exception(message)));
Assert.That(ex.Message, Is.EqualTo(message));
});
}

[Test]
public void RefreshAsync_Tests()
{
Expand Down
Loading

0 comments on commit a07621c

Please sign in to comment.