My question has changed now: I am looking for a "correct" pattern to be able to store a SynchronizationContext
or something else in the view model; so that I can marshall external invokers back onto the window's thread.
My goal:
- Get a platform agnostic object in the view model that allows marshalling back onto this window's thread.
- Be able to test if the executing code is or is not on the correct synchronization context.
If the view model has some incoming code that can be coming from an arbitrary thread, then how to marshall onto the window's thread? --- AND, I don't want to store the Dispatcher
; in order to be more platform agnostic.
I used to have a dispatcher abstraction, but I got rid of it because I thought the SynchronizationContext
in the core library would do the job better ... It is also marginally more cumbersome to deal with the abstraction; and, the SynchronizationContext
can even be subclassed itself.
So I had stored the current SynchronizationContext
in the view model constructor, and used a method to test if the current context is equal, and determine if I need to post onto the stored context. One problem with that is exhibited below: Wpf actually creates wrappers on event handlers, and the check for SynchronizationContext
equality is essentially always false.
I have also now discovered BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance
: https://learn.microsoft.com/en-us/dotnet/api/system.windows.basecompatibilitypreferences?view=netframework-4.7
But that makes me wonder about the approach, since now the default behavior is to return new instances, it may be more performant or otherwise optimal to not change what the platform default is ...
Is there some best way for the view model to be able to post, and make reliable checks? I do also want to make checks since some code needs to know if it will post asynchronously ...
I also now am toying with the concept of invoking SetSynchronizationContext
in the window constructor (because I have a window factory); but that seems potentially hacky. Perhaps I need to deal with my own abstraction again ... It is somewhat cumbersome to deal with in the view models like that.
The application code below illustrates only what I discovered: tracing startup, I am looking at the SynchronizationContext
. The main window opens on the main thread --- which is the only thread --- and there is a Dispatcher SynchronizationContext
. In a button's command handler, the callback is invoking with a different SynchronizationContext
. (But it is still on the main thread.) and, these contexts do not compare equal.
Program output:
App OnStartup: SynchronizationContext.Current: 26765710
MainWindowVIewModel ctor: SynchronizationContext.Current: 26765710
MainWindow ctor: SynchronizationContext.Current: 26765710
Command Execute: SynchronizationContext.Current: 1048160
All of the code:
using System;
using System.Threading;
using System.Windows;
namespace WpfApp1
{
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
=> Console.WriteLine(
"App OnStartup:"
+ " SynchronizationContext.Current:"
+ $" {SynchronizationContext.Current.GetHashCode()}");
}
}
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
ShutdownMode="OnMainWindowClose">
<Application.Resources/>
</Application>
using System;
using System.Threading;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Console.WriteLine(
"MainWindow ctor:"
+ " SynchronizationContext.Current:"
+ $" {SynchronizationContext.Current.GetHashCode()}");
}
}
}
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApp1="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Height="232"
Width="422">
<Window.DataContext>
<wpfApp1:MainWindowVIewModel/>
</Window.DataContext>
<Grid>
<Button Content="Button"
HorizontalAlignment="Left"
Margin="80,44,0,0"
VerticalAlignment="Top"
Width="75"
Command="{Binding Command}"/>
</Grid>
</Window>
using System;
using System.Threading;
using System.Windows.Input;
namespace WpfApp1
{
public class MainWindowVIewModel
{
public MainWindowVIewModel()
=> Console.WriteLine(
"MainWindowVIewModel ctor:"
+ " SynchronizationContext.Current:"
+ $" {SynchronizationContext.Current.GetHashCode()}");
public ICommand Command { get; }
= new Command();
}
}
using System;
using System.Threading;
using System.Windows.Input;
namespace WpfApp1
{
public class Command
: ICommand
{
public bool CanExecute(object parameter)
=> true;
public void Execute(object parameter)
=> Console.WriteLine(
"Command Execute:"
+ " SynchronizationContext.Current:"
+ $" {SynchronizationContext.Current.GetHashCode()}");
public event EventHandler CanExecuteChanged;
}
}