How to use Dispatcher for thread with WPF Window and a BlockingCollection<GraphicalData>(ConcurrentQueue<GraphicalData>)

Magnus Vinterhav 61 Reputation points
2021-01-09T16:46:45.027+00:00

Hi.

Techs used are: C#, .Net Core 3.1, WPF, BlockingCollection<> and ConcurrentQueue<>

I am trying to understand how to use the Dispatcher with a WPF Window. The Window will be updated with a ConcurrentQueue<GraphicalData> inside of a BlockingCollection<GraphicalData>. My problem is that I don't understand how to start the method that retrieves the data from the Queue.
If I start the Window with Dispatcher.Run(), then the Window is already in another thread, and I can't start the method that continuously loops to retrieve data from the Queue (and BlockingCollection).

This is an example piece of code of how I am trying to do things:

// Initializing Class to start it all  
  
		private void CreateButton_Click(object sender, RoutedEventArgs e)  
		{  
			graphQueue = new BlockingCollection<ColorAddress>(new ConcurrentQueue<ColorAddress>());  
			  
            graphHandler = new GraphicalThreadHandler(ref ags, ref logger, ref graphQueue);  
  
            graphThread = new Thread(graphHandler.RunTheGraphWindow);  
            graphThread.SetApartmentState(ApartmentState.STA);  
            graphThread.IsBackground = true;  
            graphThread.Start();  
		}  
  
// Separate class to kick off the Window in a separate thread  
public class GraphicalThreadHandler  
{  
	VisualWorld vw;  
	  
	public GraphicalThreadHandler(ref BlockingCollection<ColorAddress> graphQueue)  
	{  
		this.graphQueue = graphQueue;  
		vw = new VisualWorld(ref graphQueue);  
		vw.Show();  
	}  
  
	public void RunTheGraphWindow()  
	{  
		System.Windows.Threading.Dispatcher.Run();  
	}  
  
	public void StopTheGraphics()  
	{  
		vw.Close();  
	}  
}  
  
  
// WPF Window class  
  
public partial class VisualWorld : Window  
{  
  
	private WriteableBitmap imgBmp;  
	public BlockingCollection<ColorAddress> graphQueue;  
	// bitmap stuff  
  
	public VisualWorld(ref AreaGroupStructure ags, int x, int y, ref BlockingCollection<string> logger, ref BlockingCollection<ColorAddress> graphQueue)  
	{  
		InitializeComponent();  
		this.graphQueue = graphQueue;  
	}  
  
	public void RunGraphics()  
	{  
		foreach (ColorAddress colorAddress in graphQueue.GetConsumingEnumerable())  
		{  
			logger.Add(loggerPrefix + " Now trying to dequeue the graphQueue");  
			ColorAddressPainter(colorAddress);  
		}  
	}  
  
	public void ColorAddressPainter(ColorAddress colorAddress)  
	{  
		// update bitmap  
	}  
}  

My problem with this is that once I have started the window in the class GraphicalThreadHandler with the line System.Windows.Threading.Dispatcher.Run(); I can't figure out how to start the method RunGraphics() in the VisualWorld (WPF Window) class. This is needed to poll the Queue for graphicaldata to update the Window.

Now matter how I try, there seems to be no way to start the RunGraphics loop to retrieve the data once the Dispatcher is kicked off.

According to my friend Google, there seems to be way to use Dispatcher.BeginInvoke that may (or may not?) be used to do similar stuff, but I can't for the life of me figure out how to do that.
I don't know how to assign the Dispatcher to a variable, since it is never declared, so I can't use the dispatcher itself outside of the thread.

Any help on how to solve this is appreciated, I have been struggling with this a lot now, but I'm completely stuck.

Thank you.

PS
I asked a question related to this in https://learn.microsoft.com/en-us/answers/questions/223079/how-to-start-wpf-in-separate-thread-and-still-upda.html
but I wanted to try to present the question in another way. Hopefully this makes my challenge more clear.
DS

.NET Runtime
.NET Runtime
.NET: Microsoft Technologies based on the .NET software framework.Runtime: An environment required to run apps that aren't compiled to machine language.
1,135 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
778 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,306 Reputation points
    2021-01-10T17:37:07.513+00:00

    Hi,
    if I unterstand your question try following litte demo:

    XAML MainWindow

    <Window x:Class="WpfApp1.Window09"  
            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:local="clr-namespace:WpfApp09"  
            mc:Ignorable="d"  
            Title="Demo BlockingCollection" Height="450" Width="800">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
      <StackPanel>  
        <Button Content="Start VisualWorld" Command="{Binding}" Width="200" Margin="5"/>  
      </StackPanel>  
    </Window>  
    

    ViewModel:

    --- buggy forum software don't include this code, see attached txt file: 55058-x.txt

    XAML VisualWorld:

    <Window x:Class="WpfApp09.VisualWorld"  
            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:local="clr-namespace:WpfApp09"  
            mc:Ignorable="d"  
            Title="VisualWorld" Height="450" Width="800">  
      <Grid>  
        <Canvas x:Name="cv"/>  
      </Grid>  
    </Window>  
    

    Codebehind VisualWorld:

    using System.Collections.Concurrent;  
    using System.Threading;  
    using System.Windows;  
    using System.Windows.Media;  
    using System.Windows.Shapes;  
      
    namespace WpfApp09  
    {  
      /// <summary>  
      /// Interaction logic for VisualWorld.xaml  
      /// </summary>  
      public partial class VisualWorld : Window  
      {  
        public VisualWorld()  
        {  
          InitializeComponent();  
        }  
      
        private BlockingCollection<ColorAddress> graphQueue;  
        private Thread graphThread;  
        private Polyline pline = new Polyline();  
        private PointCollection pointCollection = new PointCollection();  
      
        public void RunGraphics(BlockingCollection<ColorAddress> graphQueue)  
        {  
          this.graphQueue = graphQueue;  
          // create initial values  
          pline.Stroke = new SolidColorBrush(Color.FromRgb(60, 125, 200));  
          pline.StrokeThickness = 1;  
          pline.Points = pointCollection;  
          for (int i = 0; i < 500; i++) pointCollection.Add(new Point(i * 4, 0));  
          cv.Children.Add(pline);  
          // start thread for painting  
          graphThread = new Thread(ColorAddressPainter);  
          graphThread.IsBackground = true;  
          graphThread.Start();  
        }  
      
        private void ColorAddressPainter()  
        {  
          foreach (ColorAddress colorAddress in graphQueue.GetConsumingEnumerable())  
          {  
            Application.Current.Dispatcher.Invoke(() =>  
            {  
              Point pt = pointCollection[colorAddress.ValueX];  
              pt.Y = colorAddress.ValueY;  
              pointCollection[colorAddress.ValueX] = pt;  
            });  
          }  
        }  
      }  
    }  
    

    Result:

    54999-x.gif

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Magnus Vinterhav 61 Reputation points
    2021-01-14T09:09:03.587+00:00

    First, sorry for my late answer. I had to debug other parts of the code for this to run.

    I have only an initial class sending data to the graphQueue, but so far it works fine. No reason to believe the others won't work as well.
    It seems the key to understanding this is in the use of the "Application.Current.Dispatcher.Invoke(() =>".

    It feels great to finally have this up and running.
    Big thanks to you PeterFleischer-3316 !

    0 comments No comments