11

I have a options window and a window that displays color based on these options and Kinect data. So far everything's on one thread (as far as I know; I haven't done any threading).

Now, I'm adding an option to open a viewer window that will need to be updated with lowest possible latency. All this entails is creating a window and showing it:

viewer = new SkeletalViewer.MainWindow();
viewer.Show();

When this event fires, the color window stops displaying colors (i.e. the event that fires 30 times a second on the main thread stops firing), but the viewer is displayed perfectly. I want the viewer and the color window to both be updated.

From reading other questions, it sounds like the solution is to create the viewer on a new thread. I'm encountering a lot of problems with this, though.

This fires when I click the button to open the viewer:

private void launchViewerThread_Click(object sender, RoutedEventArgs e)
    {
        Thread viewerThread = new Thread(delegate()
        {
            viewer = new SkeletalViewer.MainWindow();
            viewer.Dispatcher.Invoke(new Action(delegate() 
                {
                    viewer.Show();
                }));
        });

        viewerThread.SetApartmentState(ApartmentState.STA); // needs to be STA or throws exception
        viewerThread.Start();

    }

Regardless of if I just call viewer.Show() or Invoke() it as above, the line throws an exception: Cannot use a DependencyObject that belongs to a different thread than its parent Freezable. Here's how I understand Invoke(): it accesses viewer's dispatcher, which knows what thread the object is running on, and can then call methods from that thread.

Should I be trying to put this viewer on a new thread? Is the problem even a question of threads? The user will not be interacting with the viewer.

Anyone know why this doesn't work? Thanks for the help.

mikey555
  • 432
  • 3
  • 8
  • 19

3 Answers3

19

You need to call Show() on the same thread that the window is created on - that's why you are getting the error. Then you also need to start a new Dispatcher instance to get the runtime to manage the window.

private void launchViewerThread_Click(object sender, RoutedEventArgs e)
{
    Thread viewerThread = new Thread(delegate()
    {
        viewer = new SkeletalViewer.MainWindow();
        viewer.Show();
        System.Windows.Threading.Dispatcher.Run();
    });

    viewerThread.SetApartmentState(ApartmentState.STA); // needs to be STA or throws exception
    viewerThread.Start();
}

See the Multiple Windows/Multiple Threads example at: http://msdn.microsoft.com/en-us/library/ms741870.aspx

shf301
  • 31,086
  • 2
  • 52
  • 86
  • I see the the Invoke as guaranteeing that Show() is being called on viewer's thread. Even without the Invoke() around the viewer.Show(), I still get the Freezable exception. – mikey555 Jan 22 '12 at 01:33
  • @michael.greenwald Then there is something in `SkeletalViewer.MainWindow()` that is causing the exception. I built an empty WPF project that does exactly what I show above and it runs without exception. Maybe you issue is similar to the problem from this question: http://stackoverflow.com/questions/3636761/how-to-debug-this-error-when-none-of-my-code-shows-up-in-the-stack – shf301 Jan 22 '12 at 02:27
  • `System.Windows.Threading` is exists in `WindowsBase.dll` and you must add to references. – Think Big Jan 05 '18 at 18:34
1

So I was running into a similar issue where a new window failed to open on a new thread. The exception was "cannot use a dependencyobject that belongs to a different thread".

The issue ended up being that the window was using a global resource (Background brush). Once I froze the brush resource, the window loaded just fine.

bwing
  • 731
  • 8
  • 12
0

I am not sure if this will solve your problem but can you try creating a thread proc (to open a viewer window) which is executed on a different thread and then have a dispatcher.beginInvoke to update the main window ,

Here is some code-

    in the constructor register this
    public MainWindow()
    { 
       UpdateColorDelegate += UpdateColorMethod;
    } 

    // delegate and event to update color on mainwindow 
    public delegate void UpdateColorDelegate(string colorname);
    public event UpdateColorDelegate updateMainWindow;

    // launches a thread to show viewer
    private void launchViewerThread_Click(object sender, RoutedEventArgs e)
    {
        Thread t = new Thread(this.ThreadProc);
        t.Start();
    }

    // thread proc
    public void ThreadProc()
    {
       // code for viewer window
       ...
       // if you want to access any main window elements then just call DispatchToMainThread method
       DispatchToUiThread(color);   
    } 

    // 
    private void DispatchToUiThread(string color)
    {
      if (updateMainWindow != null)
      {
        object[] param = new object[1] { color};
        Dispatcher.BeginInvoke(updateMainWindow, param);
      }
    }

    // update the mainwindow control's from this method
    private void UpdateColorMethod(string colorName)
    {
       // change control or do whatever with main window controls
    } 

With this you can update the main window controls without freezing it, Let me know if you have any questions

DotNetUser
  • 6,494
  • 1
  • 25
  • 27
  • Thanks. Why do you dispatch color to the UI thread? Isn't any called method being invoked on the UI thread, the default thread? – mikey555 Jan 22 '12 at 00:54
  • because a background thread that is spun off from the main UI thread cannot update the contents of any control that was created on the UI thread. In order for the background thread to access the Control on main window, the background thread must delegate the work to the Dispatcher associated with the UI thread. This is accomplished by using either Invoke or BeginInvoke. Invoke is synchronous and BeginInvoke is asynchronous. The operation is added to the event queue of the Dispatcher at the specified DispatcherPriority. – DotNetUser Jan 22 '12 at 01:01
  • see this link - http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.begininvoke.aspx – DotNetUser Jan 22 '12 at 01:02