I have a WPF application that retrieves object 'movement' messages from a EasyNetQ/RabbitMQ message queue. Getting the message works fine as I can see in my logger.
private void btnSubscribeToMessageQ_Click(object sender, RoutedEventArgs e)
{
_logger.Debug("Listening for messages.");
_messageBus.Subscribe<RZMD.Messages.Movement>("test", HandleMovement);
}
Handling the message happens with:
private void HandleMovement(RZMD.Messages.Movement movementMessage)
{
_logger.Debug("Movement: {0}", movementMessage);
AddCirkelToCanvasWithDispatcher(movementMessage);
_movements.Add(movementMessage);
}
The UI is updated as follows by drawing a cirkel on the canvas:
private void AddCirkelToCanvasWithDispatcher(RZMD.Messages.Movement movementMessage)
{
var center = new Point(movementMessage.X, movementMessage.Y);
//var cirkel = new EllipseGeometry() { Center = center, RadiusX = 5, RadiusY = 5 }; <<
// above line causes exception re threads
// System.InvalidOperationException: 'The calling thread cannot access
// this object because a different thread owns it.'
Application.Current.Dispatcher.Invoke( ()=>
{
//if I put new EllipseGeometry() here all is fine
var cirkel = new EllipseGeometry() { Center = center, RadiusX = 5, RadiusY = 5 };
var path = new Path() { Stroke = Brushes.Black, Data = cirkel };
cnvFloor.Children.Add(path);
});
}
What I don't understand is why the call to var cirkel = new EllipsGeometry()...
throws an exception when it is placed before the Dispatcher.
There is no problem creating the center Point object of the cirkel before the dispatcher. How is the Ellipse object different?
Is this the right (modern) approach? Or are there better tools such as 'async/await', 'TPL' or 'Parallel Linq'
Also I'm adding the movements to a collection of movements. Should I investigate using an Observable collection and Notify events to draw the cirkel on the canvas instead?
How is the EllipseGeometry object different?
It is a System.Windows.Threading.DispatcherObject
, and hence has thread affinity, i.e. unless it is frozen, it can only be accessed in the thread where it was created.
Instead of creating the EllipseGeometry inside the Dispatcher action, you may freeze it to make it cross-thread accessible:
var cirkel = new EllipseGeometry() { Center = center, RadiusX = 5, RadiusY = 5 };
cirkel.Freeze();
Application.Current.Dispatcher.Invoke(() =>
{
var path = new Path() { Stroke = Brushes.Black, Data = cirkel };
cnvFloor.Children.Add(path);
});