Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
[Core] Missing await in Device.InvokeOnMainThreadAsync(Func<Task>) (#…
Browse files Browse the repository at this point in the history
…6718)

* [Core] Use local functions

* [Core] Extract DeviceUnitTests

* [Core] Ensure actually called from main thread

* [Core] Add InvokeOnMainThread tests

WithAsyncTask is expected to fail.

* [Core] Rename local wrapper functions

* [Core] Avoid ambiguous call

CS0121: The call is ambiguous between the following methods or
properties: 'Device.InvokeOnMainThreadAsync(Action)' and
'Device.InvokeOnMainThreadAsync(Func<Task>)'

dotnet/roslyn#14885
dotnet/csharplang#98
  • Loading branch information
dahlbyk authored and StephaneDelcroix committed Jul 16, 2019
1 parent 43443f4 commit ef4990b
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 22 deletions.
145 changes: 145 additions & 0 deletions Xamarin.Forms.Core.UnitTests/DeviceUnitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using Xamarin.Forms.Internals;

namespace Xamarin.Forms.Core.UnitTests
{
[TestFixture]
public class DeviceUnitTests : BaseTestFixture
{
[Test]
public void TestBeginInvokeOnMainThread()
{
bool calledFromMainThread = false;
Device.PlatformServices = MockPlatformServices(() => calledFromMainThread = true);

bool invoked = false;
Device.BeginInvokeOnMainThread(() => invoked = true);

Assert.True(invoked, "Action not invoked.");
Assert.True(calledFromMainThread, "Action not invoked from main thread.");
}

[Test]
public async Task TestInvokeOnMainThreadWithSyncFunc()
{
bool calledFromMainThread = false;
Device.PlatformServices = MockPlatformServices(() => calledFromMainThread = true);

bool invoked = false;
var result = await Device.InvokeOnMainThreadAsync(() => { invoked = true; return true; });

Assert.True(invoked, "Action not invoked.");
Assert.True(calledFromMainThread, "Action not invoked from main thread.");
Assert.True(result, "Unexpected result.");
}

[Test]
public async Task TestInvokeOnMainThreadWithSyncAction()
{
bool calledFromMainThread = false;
Device.PlatformServices = MockPlatformServices(() => calledFromMainThread = true);

bool invoked = false;
await Device.InvokeOnMainThreadAsync(() => { invoked = true; });

Assert.True(invoked, "Action not invoked.");
Assert.True(calledFromMainThread, "Action not invoked from main thread.");
}

[Test]
public async Task TestInvokeOnMainThreadWithAsyncFunc()
{
bool calledFromMainThread = false;
Device.PlatformServices = MockPlatformServices(() => calledFromMainThread = true,
invokeOnMainThread: action => Task.Delay(50).ContinueWith(_ => action()));

bool invoked = false;
var task = Device.InvokeOnMainThreadAsync(async () => { invoked = true; return true; });
Assert.True(calledFromMainThread, "Action not invoked from main thread.");
Assert.False(invoked, "Action invoked early.");

var result = await task;
Assert.True(invoked, "Action not invoked.");
Assert.True(result, "Unexpected result.");
}

[Test]
public async Task TestInvokeOnMainThreadWithAsyncFuncError()
{
bool calledFromMainThread = false;
Device.PlatformServices = MockPlatformServices(() => calledFromMainThread = true,
invokeOnMainThread: action => Task.Delay(50).ContinueWith(_ => action()));

bool invoked = false;
async Task<bool> boom() { invoked = true; throw new ApplicationException(); }
var task = Device.InvokeOnMainThreadAsync(boom);
Assert.True(calledFromMainThread, "Action not invoked from main thread.");
Assert.False(invoked, "Action invoked early.");

var aggregateEx = Assert.Throws<AggregateException>(() => task.Wait(100));
Assert.IsInstanceOf<ApplicationException>(aggregateEx.InnerException);
Assert.True(invoked, "Action not invoked.");
}

[Test]
public async Task TestInvokeOnMainThreadWithAsyncAction()
{
bool calledFromMainThread = false;
Device.PlatformServices = MockPlatformServices(() => calledFromMainThread = true,
invokeOnMainThread: action => Task.Delay(50).ContinueWith(_ => action()));

bool invoked = false;
var task = Device.InvokeOnMainThreadAsync(async () => { invoked = true; });
Assert.True(calledFromMainThread, "Action not invoked from main thread.");
Assert.False(invoked, "Action invoked early.");

await task;
Assert.True(invoked, "Action not invoked.");
}

[Test]
public async Task TestInvokeOnMainThreadWithAsyncActionError()
{
bool calledFromMainThread = false;
Device.PlatformServices = MockPlatformServices(() => calledFromMainThread = true,
invokeOnMainThread: action => Task.Delay(50).ContinueWith(_ => action()));

bool invoked = false;
async Task boom() { invoked = true; throw new ApplicationException(); }
var task = Device.InvokeOnMainThreadAsync(boom);
Assert.True(calledFromMainThread, "Action not invoked from main thread.");
Assert.False(invoked, "Action invoked early.");

var aggregateEx = Assert.Throws<AggregateException>(() => task.Wait(100));
Assert.IsInstanceOf<ApplicationException>(aggregateEx.InnerException);
Assert.True(invoked, "Action not invoked.");
}

[Test]
public void InvokeOnMainThreadThrowsWhenNull()
{
Device.PlatformServices = null;
Assert.Throws<InvalidOperationException>(() => Device.BeginInvokeOnMainThread(() => { }));
Assert.Throws<InvalidOperationException>(() => Device.InvokeOnMainThreadAsync(() => { }).Wait(100));
Assert.Throws<InvalidOperationException>(() => Device.InvokeOnMainThreadAsync(() => true).Wait(100));
Assert.Throws<InvalidOperationException>(() => Device.InvokeOnMainThreadAsync(async () => { }).Wait(100));
Assert.Throws<InvalidOperationException>(() => Device.InvokeOnMainThreadAsync(async () => true).Wait(100));
}

private IPlatformServices MockPlatformServices(Action onInvokeOnMainThread, Action<Action> invokeOnMainThread = null)
{
return new MockPlatformServices(
invokeOnMainThread: action =>
{
onInvokeOnMainThread();

if (invokeOnMainThread == null)
action();
else
invokeOnMainThread(action);
});
}
}
}
18 changes: 0 additions & 18 deletions Xamarin.Forms.Core.UnitTests/ViewUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -563,24 +563,6 @@ public void TestUnFocusedEvent ()
Assert.True (fired);
}

[Test]
public void TestBeginInvokeOnMainThread ()
{
Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: action => action ());

bool invoked = false;
Device.BeginInvokeOnMainThread (() => invoked = true);

Assert.True (invoked);
}

[Test]
public void InvokeOnMainThreadThrowsWhenNull ()
{
Device.PlatformServices = null;
Assert.Throws<InvalidOperationException>(() => Device.BeginInvokeOnMainThread (() => { }));
}

[Test]
public void TestOpenUriAction ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<Compile Include="CommandSourceTests.cs" />
<Compile Include="CommandTests.cs" />
<Compile Include="DependencyResolutionTests.cs" />
<Compile Include="DeviceUnitTests.cs" />
<Compile Include="EffectiveFlowDirectionExtensions.cs" />
<Compile Include="ShellTestBase.cs" />
<Compile Include="ShellLifeCycleTests.cs" />
Expand Down
8 changes: 4 additions & 4 deletions Xamarin.Forms.Core/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ public static Task<T> InvokeOnMainThreadAsync<T>(Func<T> func)

public static Task InvokeOnMainThreadAsync(Action action)
{
Func<object> dummyFunc = () => { action(); return null; };
return InvokeOnMainThreadAsync(dummyFunc);
string wrapAction() { action(); return null; }
return InvokeOnMainThreadAsync(wrapAction);
}

public static Task<T> InvokeOnMainThreadAsync<T>(Func<Task<T>> funcTask)
Expand All @@ -151,8 +151,8 @@ public static Task<T> InvokeOnMainThreadAsync<T>(Func<Task<T>> funcTask)

public static Task InvokeOnMainThreadAsync(Func<Task> funcTask)
{
Func<Task<object>> dummyFunc = async () => { await funcTask().ConfigureAwait(false); return null; };
return InvokeOnMainThreadAsync(dummyFunc);
async Task<object> wrapFunction() { await funcTask().ConfigureAwait(false); return null; }
return InvokeOnMainThreadAsync(wrapFunction);
}

public static async Task<SynchronizationContext> GetMainThreadSynchronizationContextAsync()
Expand Down

0 comments on commit ef4990b

Please sign in to comment.