Skip to content

Commit

Permalink
Added ThreadHelper
Browse files Browse the repository at this point in the history
Now to switch to the UIThread, you run `await ThreadHelper.SwitchToMainThreadAsync();`
  • Loading branch information
Clancey committed Oct 17, 2019
1 parent cf293f4 commit 868c398
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 42 deletions.
14 changes: 6 additions & 8 deletions sample/Comet.Samples/ProgressBarSample1.cs
Expand Up @@ -9,16 +9,14 @@ public class ProgressBarSample1 : View

public ProgressBarSample1()
{
_timer = new Timer(state =>
{
_timer = new Timer(async state => {
var p = (State<double>)state;
Device.InvokeOnMainThread(() =>
{
var current = p.Value;
var value = current < 101 ? current + 1 : 0;
await ThreadHelper.SwitchToMainThreadAsync();
p.Value = value;
});
var current = p.Value;
var value = current < 101 ? current + 1 : 0;
p.Value = value;
}, percentage, 100, 100);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Comet.Android/UI.cs
Expand Up @@ -48,7 +48,7 @@ public static void Init()
ModalView.PerformDismiss = ModalManager.DismisModal;

// Device Services
Device.PerformInvokeOnMainThread = (a) => AndroidContext.CurrentContext.RunOnUiThread(a);
ThreadHelper.JoinableTaskContext = new Microsoft.VisualStudio.Threading.JoinableTaskContext();
Device.GraphicsService = new AndroidGraphicsService();
Device.BitmapService = new AndroidBitmapService();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Comet.Blazor/UI.cs
Expand Up @@ -38,7 +38,7 @@ public static void Init()
Registrar.Handlers.Register<ViewRepresentable, UnsupportedHandler<ViewRepresentable>>();
Registrar.Handlers.Register<WebView, UnsupportedHandler<WebView>>();

Device.PerformInvokeOnMainThread = a => a();
ThreadHelper.JoinableTaskContext = new Microsoft.VisualStudio.Threading.JoinableTaskContext();
ListView.HandlerSupportsVirtualization = false;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Comet.Mac/UI.cs
Expand Up @@ -41,7 +41,7 @@ public static void Init()
Registrar.Handlers.Register<Grid, GridHandler>();

// Device Features
Device.PerformInvokeOnMainThread = _invoker.BeginInvokeOnMainThread;
ThreadHelper.JoinableTaskContext = new Microsoft.VisualStudio.Threading.JoinableTaskContext();
Device.FontService = new MacFontService();
Device.GraphicsService = new MacGraphicsService();
Device.BitmapService = new MacBitmapService();
Expand Down
2 changes: 1 addition & 1 deletion src/Comet.UWP/UI.cs
Expand Up @@ -45,7 +45,7 @@ public static void Init()
Registrar.Handlers.Register<ZStack, ManagedZStackHandler>();
Registrar.Handlers.Register<Grid, ManagedGridHandler>();

Device.PerformInvokeOnMainThread = async a => await GetDispatcher().RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => a());
ThreadHelper.JoinableTaskContext = new Microsoft.VisualStudio.Threading.JoinableTaskContext();

Device.BitmapService = new UWPBitmapService();

Expand Down
2 changes: 1 addition & 1 deletion src/Comet.WPF/UI.cs
Expand Up @@ -41,7 +41,7 @@ public static void Init()

Device.BitmapService = new WPFBitmapService();

Device.PerformInvokeOnMainThread = a => Application.Current.Dispatcher.Invoke(a);
ThreadHelper.JoinableTaskContext = new Microsoft.VisualStudio.Threading.JoinableTaskContext();

ListView.HandlerSupportsVirtualization = false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Comet.iOS/UI.cs
Expand Up @@ -52,8 +52,8 @@ public static void Init ()
};
ModalView.PerformDismiss = () => PresentingViewController.DismissModalViewController (true);

// Device Features
Device.PerformInvokeOnMainThread = _invoker.BeginInvokeOnMainThread;
ThreadHelper.JoinableTaskContext = new Microsoft.VisualStudio.Threading.JoinableTaskContext();

Device.FontService = new iOSFontService();
Device.GraphicsService = new iOSGraphicsService();
Device.BitmapService = new iOSBitmapService();
Expand Down
3 changes: 3 additions & 0 deletions src/Comet/Comet.csproj
Expand Up @@ -18,6 +18,9 @@
<Folder Include="Gestures\" />
<Folder Include="Exceptions\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="16.4.16" />
</ItemGroup>
<ProjectExtensions>
<MonoDevelop>
<Properties>
Expand Down
9 changes: 4 additions & 5 deletions src/Comet/Controls/View.cs
Expand Up @@ -254,13 +254,12 @@ internal override void ContextPropertyChanged(string property, object value, boo
ViewPropertyChanged(property, value);
}

public static void SetGlobalEnvironment(string key, object value)
public static async void SetGlobalEnvironment(string key, object value)
{
Environment.SetValue(key, value);
Device.InvokeOnMainThread(() =>
{
ActiveViews.ForEach(x => x.ViewPropertyChanged(key, value));
});
await ThreadHelper.SwitchToMainThreadAsync();
ActiveViews.ForEach(x => x.ViewPropertyChanged(key, value));

}
public static void SetGlobalEnvironment(IDictionary<string, object> data)
{
Expand Down
16 changes: 0 additions & 16 deletions src/Comet/Device.cs
Expand Up @@ -8,22 +8,6 @@ namespace Comet
{
public static class Device
{

static Device()
{
mainThread = Thread.CurrentThread;
}

public static Action<Action> PerformInvokeOnMainThread;
internal static Thread mainThread;
public static void InvokeOnMainThread(Action action)
{
if (mainThread == Thread.CurrentThread)
action();
else
PerformInvokeOnMainThread(action);
}

public static IFontService FontService = new FallbackFontService();
public static IGraphicsService GraphicsService;
public static IBitmapService BitmapService;
Expand Down
4 changes: 2 additions & 2 deletions src/Comet/EnvironmentAware.cs
Expand Up @@ -124,7 +124,7 @@ public static T SetEnvironment<T>(this T contextualObject, Type type, string key
var typedKey = ContextualObject.GetTypedKey(type, key);
contextualObject.SetValue(typedKey, value, cascades);
//TODO: Verify this is needed
Device.InvokeOnMainThread(() => {
ThreadHelper.RunOnMainThread(() => {
contextualObject.ContextPropertyChanged(typedKey, value,cascades);
});
return contextualObject;
Expand All @@ -135,7 +135,7 @@ public static T SetEnvironment<T>(this T contextualObject, string key, object va
{
if(!contextualObject.SetValue(key, value, cascades))
return contextualObject;
Device.InvokeOnMainThread(() =>
ThreadHelper.RunOnMainThread(() =>
{
contextualObject.ContextPropertyChanged(key, value,cascades);
});
Expand Down
199 changes: 199 additions & 0 deletions src/Comet/Helpers/ThreadHelper.cs
@@ -0,0 +1,199 @@
using Microsoft.VisualStudio.Threading;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Comet
{
public class ThreadHelper
{
static JoinableTaskContext joinableTaskContext;
public static JoinableTaskContext JoinableTaskContext {
get => joinableTaskContext ?? (joinableTaskContext = new JoinableTaskContext());
set => joinableTaskContext = value;
}


//
// Summary:
// Gets an awaitable whose continuations execute on the synchronization context
// that this instance was initialized with, in such a way as to mitigate both deadlocks
// and reentrancy.
//
// Parameters:
// alwaysYield:
// A value indicating whether the caller should yield even if already executing
// on the main thread.
//
// cancellationToken:
// A token whose cancellation will immediately schedule the continuation on a threadpool
// thread.
//
// Returns:
// An awaitable.
//
// Exceptions:
// T:System.OperationCanceledException:
// Thrown back at the awaiting caller from a background thread when cancellationToken
// is canceled before any required transition to the main thread is complete. No
// exception is thrown if alwaysYield is false and the caller was already on the
// main thread before calling this method, or if the main thread transition completes
// before the thread pool responds to cancellation.
//
// Remarks:
// private async Task SomeOperationAsync() { // This first part can be on the caller's
// thread, whatever that is. DoSomething(); // Now switch to the Main thread to
// talk to some STA object. // Supposing it is also important to *not* do this step
// on our caller's callstack, // be sure we yield even if we're on the UI thread.
// await this.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true); STAService.DoSomething();
// }
public static JoinableTaskFactory.MainThreadAwaitable SwitchToMainThreadAsync(bool alwaysYield, CancellationToken cancellationToken = default) => JoinableTaskContext.Factory.SwitchToMainThreadAsync(alwaysYield,cancellationToken);

//
// Summary:
// Gets an awaitable whose continuations execute on the synchronization context
// that this instance was initialized with, in such a way as to mitigate both deadlocks
// and reentrancy.
//
// Parameters:
// cancellationToken:
// A token whose cancellation will immediately schedule the continuation on a threadpool
// thread (if the transition to the main thread is not already complete). The token
// is ignored if the caller was already on the main thread.
//
// Returns:
// An awaitable.
//
// Exceptions:
// T:System.OperationCanceledException:
// Thrown back at the awaiting caller from a background thread when cancellationToken
// is canceled before any required transition to the main thread is complete. No
// exception is thrown if the caller was already on the main thread before calling
// this method, or if the main thread transition completes before the thread pool
// responds to cancellation.
//
// Remarks:
// private async Task SomeOperationAsync() { // on the caller's thread. await DoAsync();
// // Now switch to a threadpool thread explicitly. await TaskScheduler.Default;
// // Now switch to the Main thread to talk to some STA object. await this.JobContext.SwitchToMainThreadAsync();
// STAService.DoSomething(); }
public static JoinableTaskFactory.MainThreadAwaitable SwitchToMainThreadAsync(CancellationToken cancellationToken = default) => JoinableTaskContext.Factory.SwitchToMainThreadAsync(cancellationToken);


public static async void RunOnMainThread(Action action)
{
await SwitchToMainThreadAsync();
action();
}

//
// Summary:
// Runs the specified asynchronous method to completion while synchronously blocking
// the calling thread.
//
// Parameters:
// asyncMethod:
// The asynchronous method to execute.
//
// Type parameters:
// T:
// The type of value returned by the asynchronous operation.
//
// Returns:
// The result of the Task returned by asyncMethod.
//
// Remarks:
// Any exception thrown by the delegate is rethrown in its original type to the
// caller of this method.
// When the delegate resumes from a yielding await, the default behavior is to resume
// in its original context as an ordinary async method execution would. For example,
// if the caller was on the main thread, execution resumes after an await on the
// main thread; but if it started on a threadpool thread it resumes on a threadpool
// thread.
// See the Microsoft.VisualStudio.Threading.JoinableTaskFactory.Run(System.Func{System.Threading.Tasks.Task})
// overload documentation for an example.
public static T Run<T>(Func<Task<T>> asyncMethod) => JoinableTaskContext.Factory.Run(asyncMethod);
//
// Summary:
// Runs the specified asynchronous method to completion while synchronously blocking
// the calling thread.
//
// Parameters:
// asyncMethod:
// The asynchronous method to execute.
//
// Remarks:
// Any exception thrown by the delegate is rethrown in its original type to the
// caller of this method.
// When the delegate resumes from a yielding await, the default behavior is to resume
// in its original context as an ordinary async method execution would. For example,
// if the caller was on the main thread, execution resumes after an await on the
// main thread; but if it started on a threadpool thread it resumes on a threadpool
// thread.
// // On threadpool or Main thread, this method will block // the calling thread
// until all async operations in the // delegate complete. joinableTaskFactory.Run(async
// delegate { // still on the threadpool or Main thread as before. await OperationAsync();
// // still on the threadpool or Main thread as before. await Task.Run(async delegate
// { // Now we're on a threadpool thread. await Task.Yield(); // still on a threadpool
// thread. }); // Now back on the Main thread (or threadpool thread if that's where
// we started). });
public static void Run(Func<Task> asyncMethod) => JoinableTaskContext.Factory.Run(asyncMethod);

//
// Summary:
// Invokes an async delegate on the caller's thread, and yields back to the caller
// when the async method yields. The async delegate is invoked in such a way as
// to mitigate deadlocks in the event that the async method requires the main thread
// while the main thread is blocked waiting for the async method's completion.
//
// Parameters:
// asyncMethod:
// The method that, when executed, will begin the async operation.
//
// Returns:
// An object that tracks the completion of the async operation, and allows for later
// synchronous blocking of the main thread for completion if necessary.
//
// Remarks:
// Exceptions thrown by the delegate are captured by the returned Microsoft.VisualStudio.Threading.JoinableTask.
// When the delegate resumes from a yielding await, the default behavior is to resume
// in its original context as an ordinary async method execution would. For example,
// if the caller was on the main thread, execution resumes after an await on the
// main thread; but if it started on a threadpool thread it resumes on a threadpool
// thread.
public static JoinableTask RunAsync(Func<Task> asyncMethod) => JoinableTaskContext.Factory.RunAsync(asyncMethod);

//
// Summary:
// Invokes an async delegate on the caller's thread, and yields back to the caller
// when the async method yields. The async delegate is invoked in such a way as
// to mitigate deadlocks in the event that the async method requires the main thread
// while the main thread is blocked waiting for the async method's completion.
//
// Parameters:
// asyncMethod:
// The method that, when executed, will begin the async operation.
//
// Type parameters:
// T:
// The type of value returned by the asynchronous operation.
//
// Returns:
// An object that tracks the completion of the async operation, and allows for later
// synchronous blocking of the main thread for completion if necessary.
//
// Remarks:
// Exceptions thrown by the delegate are captured by the returned Microsoft.VisualStudio.Threading.JoinableTask.
// When the delegate resumes from a yielding await, the default behavior is to resume
// in its original context as an ordinary async method execution would. For example,
// if the caller was on the main thread, execution resumes after an await on the
// main thread; but if it started on a threadpool thread it resumes on a threadpool
// thread.

public static JoinableTask<T> RunAsync<T>(Func<Task<T>> asyncMethod) => JoinableTaskContext.Factory.RunAsync(asyncMethod);


}
}
6 changes: 4 additions & 2 deletions src/Comet/HotReloadHelper.cs
Expand Up @@ -106,11 +106,13 @@ public static void RegisterReplacedView(string oldViewType, Type newViewType)
}
}

public static void TriggerReload()
public static async void TriggerReload()
{
var roots = View.ActiveViews.Where (x => x.Parent == null).ToList();

await ThreadHelper.SwitchToMainThreadAsync();
foreach(var view in roots) {
Device.InvokeOnMainThread (view.Reload);
view.Reload();
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Comet.Tests/UI.cs
Expand Up @@ -22,8 +22,8 @@ public static void Init ()
Registrar.Handlers.Register<ListView, GenericViewHandler> ();
Registrar.Handlers.Register<View, GenericViewHandler> ();
Registrar.Handlers.Register<ContentView, GenericViewHandler> ();
Device.PerformInvokeOnMainThread = (a) => a ();

ThreadHelper.JoinableTaskContext = new Microsoft.VisualStudio.Threading.JoinableTaskContext();
HotReloadHelper.IsEnabled = true;

}
Expand Down

0 comments on commit 868c398

Please sign in to comment.