wyvern010
Topic Author
Posts: 31
Joined: 18 Apr 2019, 13:41

Update UI from different thread.

15 Sep 2021, 17:25

Hi,

I'm trying to update the Ui from a different thread.
Thought the simplest way todo this would be through the OnPropertyChanged function like so:
protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            if (Dispatcher.CurrentDispatcher.CheckAccess())
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            else
            {
                //Dispatcher.CurrentDispatcher.Invoke(() => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); });
                Dispatcher.CurrentDispatcher.Invoke(() => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); });
            }
        }
        
But it seems that CheckAccess() always returns true no matter from which thread its called.

So i implemented my own "Thread-id-watcher" like so:
protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            if (_threadId == Thread.CurrentThread.ManagedThreadId)
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            else
            {
                //Dispatcher.CurrentDispatcher.BeginInvoke(() => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }); //no effect at all.
                Dispatcher.CurrentDispatcher.Invoke(() => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); });
            }
        }

        public int _threadId;
        public ViewModelBase()
        {
            _threadId = Thread.CurrentThread.ManagedThreadId;
        }
Now the call from the Non-UI thread correctly goes to the "else" but it is still a hit and miss in that i get a "System.AccessViolationException".
When its a hit and the ui actually gets updated the console puts out:
Error : The calling thread (18080) cannot access this Slidecrew_Noesis.MainWindow because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this View because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this Border because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this View because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this ContentPresenter because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this View because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this Grid because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this View because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this StackPanel because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this Grid because a different thread (36032) owns it
Error : The calling thread (18080) cannot access this View because a different thread (36032) owns it

My 2 button commands:
  public void OnCommand(object arg)
        {
            ButtonText = "Called from UI Thread";
        }

        public void OnCommandNewThread(object arg)
        {
            new Thread(new ThreadStart(() => { ButtonText = "Called from NON-UI Thread"; })).Start();
        }

EDIT:

I believe i fixed it.
The trick was to cache the SynchronizationContext during initializiation. And use that to send functions to the correct thread.
protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            if (_dispatcher == Dispatcher.CurrentDispatcher.SynchronizationContext)
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
            else
            {
                _dispatcher.Post(new SendOrPostCallback((e) => 
                {
                    PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
                }), name);
            }
        }

        private SynchronizationContext _dispatcher;
        public ViewModelBase()
        {
            _dispatcher = Dispatcher.CurrentDispatcher.SynchronizationContext;
        }

        public event PropertyChangedEventHandler PropertyChanged;
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: Update UI from different thread.

16 Sep 2021, 12:34

I was going to say that you shouldn't use Dispatcher.CurrentDispatcher.CheckAccess() because that returns the dispatcher for the current thread, so it will always have access.
And that you would probably need to store the Dispatcher used for the UI, so you can check against it, something like:
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
  if (TheUIDispatcher.CheckAccess())
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  else
  {
    TheUIDispatcher.Invoke(() => { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); });
  }
}
 
User avatar
jsantos
Site Admin
Posts: 3905
Joined: 20 Jan 2012, 17:18
Contact:

Re: Update UI from different thread.

16 Sep 2021, 13:11

Besides the issue with the dispatcher, async DataContexts are tricky. Make sure you are implementing them right. There is a good article on the MSDN.

I am going to mark this as solved.

Who is online

Users browsing this forum: Google [Bot] and 90 guests