0

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:

  1. Get a platform agnostic object in the view model that allows marshalling back onto this window's thread.
  2. 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;
    }
}
Steven Coco
  • 542
  • 6
  • 16
  • 1
    Is there a specific issue that this causes? It seems like it would be fine as long as the DispatcherSynchronizationContext (assuming it is of this type) is associated with the UI thread's dispatcher. – Mike Zboray May 03 '18 at 03:04
  • `Dispatcher.Invoke` or `Dispatcher.BeginInvoke` are usually much better choices for marshaling to the UI thread in WPF. Is there a specific reason you are trying to use `SyncronizationContext` instead? – Bradley Uffner May 03 '18 at 03:10
  • The issue is with wanting to test the context: the view model has some event handler, and that invoker that has raised an event that I'm handling is coming in from an arbitrary thread, and I want to marshal onto the window's thread to raise my property change event. SO, I capture the context when the view model is constructed; and then I must test the current context against it to know if I need to marshall back onto it. And the reason I'm using the context and not the dispatcher is that the view model has no knowledge of the dispatcher --- it can get the sync context. – Steven Coco May 03 '18 at 03:31
  • In your current code, the view-model doesn't store the main thread's `SynchronizationContext`. – dymanoid May 03 '18 at 09:23

0 Answers0