I am using a visual control in my project that is from a library that I do not have the source to.
It takes too long to update (200ms, roughly) for good UI responsiveness with three of these controls on-screen at once. (I might need to update all three at once, which leaves my UI stuck for ~600ms while they are all thinking). 
I have read some posts about TaskSchedulers, and am beginning to investigate the Parallel task features as a way of running each of these controls in their own thread. The platform will be multi-core, so I want to take advantage of simultaineous processing.
The problem is that I don't even know what I don't know about how to go about this, though..
Is there a suitable design pattern for running a control in a separate thread from the main UI thread in WPF?
Specifically: it is a third party map control, that when given a new location or zoom level takes far too long to redraw (~200ms). With perhaps three of these updating at a maximum of 4Hz - obviously they won't keep up..
I have encapsulated the WPF control in a usercontrol, and need to run each instance in it's own thread, while still capturing user input (mouse clicks, for example).
UPDATE: while I am feeling around for a solution, I have implemented the following so far.
My main (UI) thread spawns a thread that creates a new window that contains the control in question, and locates it in the correct position (so that it looks like it is just a normal control).
_leftTopThread = new Thread(() =>
{
   _topLeftMap = new MapWindow()
   {
      WindowStartupLocation = WindowStartupLocation.Manual,
      Width = leftLocation.Width,
      Height = leftLocation.Height,
      Left = leftLocation.X,
      Top = leftLocation.Y,
      CommandQueue = _leftMapCommandQueue,
   };
   _topLeftMap.Show();
   System.Windows.Threading.Dispatcher.Run();
});
_leftTopThread.SetApartmentState(ApartmentState.STA);
_leftTopThread.IsBackground = true;
_leftTopThread.Name = "LeftTop";
_leftTopThread.Start();
Where CommandQueue is a Thread-safe BlockingCollection Queue for sending commands to the map (moving the location, etc).
The problem is now that I can either
System.Windows.Threading.Dispatcher.Run() callI can't spin waiting for commands, because it would soak up all my thread CPU!
Is it possible to block and have the event message-pump working?
If you want to access a control on a wpf form from another assembly you have to use the modifier attribute x:FieldModifier="public" or use the method proposed by Jean. Save this answer. Show activity on this post. This may be a slightly different answer, but let's think about why we need to pass data between forms.
Multiple Windows, Multiple ThreadsSome WPF applications require multiple top-level windows. It is perfectly acceptable for one Thread/Dispatcher combination to manage multiple windows, but sometimes several threads do a better job.
The thread affinity is handled by the Dispatcher class, a prioritized message loop for WPF applications. Typically your WPF projects have a single Dispatcher object (and therefore a single UI thread) that all user interface work is channeled through.
Well, I have a method that works - but it may well not be the most elegant..
I have a window that contains my third party (slow-rendering) control in the XAML.
public partial class MapWindow : Window
{
    private ConcurrentQueue<MapCommand> _mapCommandQueue;
    private HwndSource _source;
    // ...
}
My main (UI) thread contructs and starts this window on a thread:
_leftTopThread = new Thread(() =>
{
   _topLeftMap = new MapWindow()
   {
      WindowStartupLocation = WindowStartupLocation.Manual,
      CommandQueue = _leftMapCommendQueue,
   };
    _topLeftMap.Show();
    System.Windows.Threading.Dispatcher.Run();
});
_leftTopThread.SetApartmentState(ApartmentState.STA);
_leftTopThread.IsBackground = true;
_leftTopThread.Name = "LeftTop";
_leftTopThread.Start();
I then get a handle to the window in the thread (after it has initialised):
private IntPtr LeftHandMapWindowHandle
{
    get
    {
        if (_leftHandMapWindowHandle == IntPtr.Zero)
        {
            if (!_topLeftMap.Dispatcher.CheckAccess())
            {
                _leftHandMapWindowHandle = (IntPtr)_topLeftMap.Dispatcher.Invoke(
                  new Func<IntPtr>(() => new WindowInteropHelper(_topLeftMap).Handle)
                );
            }
            else
            {
                _leftHandMapWindowHandle = new WindowInteropHelper(_topLeftMap).Handle;
            }
        }
        return _leftHandMapWindowHandle;
    }
}
.. and after putting a command onto the thread-safe queue that is shared with the threaded window:
var command = new MapCommand(MapCommand.CommandType.AircraftLocation, new object[] {RandomLatLon});
_leftMapCommendQueue.Enqueue(command);
.. I let it know it can check the queue:
PostMessage(LeftHandMapWindowHandle, MapWindow.WmCustomCheckForCommandsInQueue, IntPtr.Zero, IntPtr.Zero);
The window can receive my message because it has hooked into the window messages:
protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);
    _source = PresentationSource.FromVisual(this) as HwndSource;
    if (_source != null) _source.AddHook(WndProc);
}
..which it then can check:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) // 
{
    // Handle messages...
    var result = IntPtr.Zero;
    switch (msg)
    {
        case WmCustomCheckForCommandsInQueue:
            CheckForNewTasks();
            break;
    }
    return result;
}
..and then execute on the thread!
private void CheckForNewTasks()
{
    MapCommand newCommand;
    while (_mapCommandQueue.TryDequeue(out newCommand))
    {
        switch (newCommand.Type)
        {
            case MapCommand.CommandType.AircraftLocation:
                SetAircraftLocation((LatLon)newCommand.Arguments[0]);
                break;
            default:
                Console.WriteLine(String.Format("Unknown command '0x{0}'for window", newCommand.Type));
                break;
        }
    }
}
Easy as that.. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With