We are developing a WPF application which will open a number of reports at the same time (just like a typical MDI application such as Excel or Visual Studio). Although it is possible to have the data context for those reports run in multiple worker threads, we still find that if the number of opened reports is really big, even the rendering of those reports (basically UserControl hosted either in a MDI environment or just in a grid area in the main view) will still make the application less responsive.
So, my idea is to at least have several areas in the main UI, each of whom will have its user control running in different UI threads. Again, imagine a typical view in visual studio, except for the menus, it has the main area of text editor, a side area which hosts for example solution explorer, and a bottom area which hosts for example error list and output. So I want these three areas to be running in three UI threads (but naturally they are hosted in one MainView, that's the part I am not sure about).
I am asking because I know it is possible to have several (top-level) windows running in different UI threads. But somebody said it doesn't apply to the user controls. Is it true? If so, what is the typical solution to my scenario, i.e., the number of opened UserControl is really big, and many of these UserControl are real-time so rendering of them takes huge amount of resources? Thanks!
Background Information on UI Threading Models
Normally an application has one "main" UI thread...and it may have 0 or more background/worker/non-UI threads where you (or the .NET runtime/framework) does background work.
(...there's a another special thread in WPF called the rendering thread but I will skip that for now...)
For example, a simple WPF Application might have this list of threads:
And a simple WinForms Application might have this list of threads:
When you create an element it is tied (has affinity) to a particular Dispatcher & thread and can only be accessed safely from the thread associated with the Dispatcher.
If you try and access properties or methods of an object from a different thread, you will usually get an exception e.g. in WPF:
In WindowsForms:
Detecting whether on UI thread in WPF and Winforms
http://www.perceler.com/articles1.php?art=crossthreads1
Any modifications to the UI need to be performed on the same thread on which a UI element was created...so background threads use Invoke/BeginInvoke to get that work run on the UI thread.
Demo to Demonstrate Issues with Element Creation on non-UI Thread
<Window x:Class="WpfApplication9.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<StackPanel x:Name="mystackpanel">
</StackPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
namespace WpfApplication9
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Thread m_thread1;
Thread m_thread2;
Thread m_thread3;
Thread m_thread4;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
CreateAndAddElementInDifferentWays();
}
void CreateAndAddElementInDifferentWays()
{
string text = "created in ui thread, added in ui thread [Main STA]";
System.Diagnostics.Debug.WriteLine(text);
CreateAndAddTextChild(text);
// Do NOT use any Joins with any of these threads, otherwise you will get a
// deadlock on any "Invoke" call you do.
// To better observe and focus on the behaviour when creating and
// adding an element from differently configured threads, I suggest
// you pick "one" of these and do a recompile/run.
ParameterizedThreadStart paramthreadstart1 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnThread);
m_thread1 = new Thread(paramthreadstart1);
m_thread1.SetApartmentState(ApartmentState.STA);
m_thread1.Start("[STA]");
//ParameterizedThreadStart paramthreadstart2 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnUIThread);
//m_thread2 = new Thread(paramthreadstart2);
//m_thread2.SetApartmentState(ApartmentState.STA);
//m_thread2.Start("[STA]");
//ParameterizedThreadStart paramthreadstart3 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnThread);
//m_thread3 = new Thread(paramthreadstart3);
//m_thread3.SetApartmentState(ApartmentState.MTA);
//m_thread3.Start("[MTA]");
//ParameterizedThreadStart paramthreadstart4 = new ParameterizedThreadStart(this.WorkCreatedOnThreadAddedOnUIThread);
//m_thread4 = new Thread(paramthreadstart4);
//m_thread4.SetApartmentState(ApartmentState.MTA);
//m_thread4.Start("[MTA]");
}
//----------------------------------------------------------------------
void WorkCreatedOnThreadAddedOnThread(object parameter)
{
string threadingmodel = parameter as string;
string text = "created in worker thread, added in background thread, " + threadingmodel;
System.Diagnostics.Debug.WriteLine(text);
CreateAndAddTextChild(text);
}
void WorkCreatedOnThreadAddedOnUIThread(object parameter)
{
string threadingmodel = parameter as string;
string text = "created in worker thread, added in ui thread via invoke" + threadingmodel;
System.Diagnostics.Debug.WriteLine(text);
TextBlock tb = CreateTextBlock(text);
if (tb != null)
{
// You can alternatively use .Invoke if you like!
DispatcherOperation dispop = Dispatcher.BeginInvoke(new Action(() =>
{
// Get this work done on the main UI thread.
AddTextBlock(tb);
}));
if (dispop.Status != DispatcherOperationStatus.Completed)
{
dispop.Wait();
}
}
}
//----------------------------------------------------------------------
public TextBlock CreateTextBlock(string text)
{
System.Diagnostics.Debug.WriteLine("[CreateTextBlock]");
try
{
TextBlock tb = new TextBlock();
tb.Text = text;
return tb;
}
catch (InvalidOperationException ex)
{
// will always exception, using this to highlight issue.
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
public void AddTextBlock(TextBlock tb)
{
System.Diagnostics.Debug.WriteLine("[AddTextBlock]");
try
{
mystackpanel.Children.Add(tb);
}
catch (InvalidOperationException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
public void CreateAndAddTextChild(string text)
{
TextBlock tb = CreateTextBlock(text);
if (tb != null)
AddTextBlock(tb);
}
}
}
Secondary UI thread aka "Creating a top-level Window on another thread"
It's possible to create secondary UI-threads, so long as you mark the thread as using the STA apartment model, and create a Dispatcher (e.g. use Dispatcher.Current) and start a "run" loop (Dispatcher.Run()) so the Dispatcher can service messages for the UI elements created on that thread.
Dispatcher to Thread relationships in WPF
http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/
http://www.diranieh.com/NET_WPF/Threading.htm
BUT an element created in one UI thread can't be put into the
logical/visual tree of another element which is created on a different
UI thread.
Workaround Technique for mixing elements created on different UI threads
There is a limited workaround technique, which may provide you with some ability to compose the rendering of an element created in one UI thread with the visual tree created in a different thread...by using HostVisual. See this example:
https://dwayneneed.github.io/wpf/2007/04/26/multithreaded-ui-hostvisual.html
No, UserControls are tied to the UI thread. Even if you were able to initialize them elsewhere, you'd have issues adding them to the main UI as they'd belong to a different thread.
You CAN split rendering of the visual tree across different threads.
See this article for a good explanation and an example that renders video output.
http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
But doing this is only really justifiable when the actual rendering of the Visual is implemented elsewhere or it's technologically very odd in a WPF application such as rendering a Direct3D scene as a Visual.
The important note here, as is mentioned in the article, is that if the secondary threads render WPF XAML then you loose input events because routed events can't cross the thread boundary.
Related
I'd like to load Xaml from code running in a background thread.
I understand I would have to sync with the dispatcher. However, it fails (throws an exception).
Why?
Here is the code
public MainWindow()
{
InitializeComponent();
Thread thread = new Thread(new ThreadStart(delegate
{
Dispatcher.Invoke(new Action(delegate
{
Content = XamlReader.Parse(
"<Button xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
Content='Hello World'/>");
}));
}));
thread.Start();
}
As Pavlo mentioned, you need to also set your content within the Dispatcher.
However, I will say - this is fairly useless.
Remember, when you call Dispatcher.Invoke or BeginInvoke, you're explicitly saying to run that code on the UI thread. By starting a background thread that does nothing but invoke back to the UI thread, you're effectively doing the same work on the UI thread, with the disadvantage of extra overhead being added to the system as well as harder debugging. In this case, you should just load the file directly.
This seems like a bad idea for a couple reasons. Are you expecting to get XAML fragments from a database or some other storage and you can't create the instances of these controls in C#? You could just create a Button directly.
Are you able to allow the data to drive your visualization? A good example of this is having a collection of ICommand objects (CommandViewModel, RelayCommand, etc) and a CommandView that you want to use to represent your command? In this case, it could be a <Button> with a binding to the CommandViewModel Title or Content property.
You're background thread could then be used to drive the population of data (collections, properties) and you're UI would be designed to flexibly accommodate the expected data patterns.
I use a browse for files dialog to allow a user to select multiple images. If a lot of images are selected, as expected it takes a bit. Below is an example of what I do with the selected images. I loop through the filepaths to images and create an instance of a user control, the user control has an Image control and a few other controls. I create the instance of this control then add it to a existing stackPanel created in the associating window xaml file. The example just below works fine, but I'm trying to understand BackGroundWorker better, I get the basics of how to set it up, with it's events, and pass back a value that could update a progress bar, but because my loop that takes up time below adds the usercontrol instance to an existing stackPanel, It won't work, being in a different thread. Is BackGroundWorker something that would work for an example like this? If so, what's the best way to update the ui (my stackpanel) that is outside the thread. I'm fairly new to wpf and have never used the BackGroundWorker besides testing having it just update progress with a int value, so I hope this question makes sense, if I'm way off target just let me know. Thanks for any thoughts.
Example of how I'm doing it now, which does work fine.
protected void myMethod(string[] fileNames) {
MyUserControl uc;
foreach (String imagePath in fileNames) {
uc = new MyUserControl();
uc.setImage(imagePath);
stackPanel.Children.Add(uc);
progressBar.Value = ++counter;
progressBar.Refresh();
}
}
below this class i have this so I can have the progressBar refresh:
public static class extensionRefresh {
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement) {
uiElement.Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
}
}
Check out this article on
Building more responsive apps with the Dispatcher
Now that you have a sense of how the Dispatcher works, you might be surprised to know that you will not find use for it in most cases. In Windows Forms 2.0, Microsoft introduced a class for non-UI thread handling to simplify the development model for user interface developers. This class is called the BackgroundWorker
In WPF, this model is extended with a DispatcherSynchronizationContext class. By using BackgroundWorker, the Dispatcher is being employed automatically to invoke cross-thread method calls. The good news is that since you are probably already familiar with this common pattern, you can continue using BackgroundWorker in your new WPF projects
Basically the approach is
BackgroundWorker _backgroundWorker = new BackgroundWorker();
// Set up the Background Worker Events
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
// Run the Background Worker
_backgroundWorker.RunWorkerAsync(5000);
// Worker Method
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Do something
}
// Completed Method
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Doing UI stuff
if (e.Cancelled)
{
statusText.Text = "Cancelled";
}
else if (e.Error != null)
{
statusText.Text = "Exception Thrown";
}
else
{
statusText.Text = "Completed";
}
}
Using a BackgroundWorker alone won't solve your issue since elements created during the DoWork portion will still have originated from a non-UI thread. You must call Freeze on any objects you intend to use on another thread. However only certain UI objects will be freezable. You may have to load in the images as BitmapImages on the background thread, then create the rest of your user control on the UI thread. This may still accomplish your goals, since loading in the image is probably the most heavyweight operation.
Just remember to set BitmapImage.CacheOption to OnLoad, so it actually loads up the image when you create the object rather than waiting until it needs to be displayed.
my issue is the following:
I have a windows form in which I've placed a LayoutPanel, when the forms Loads, multiple controls like: textboxes and labels are being added to the LayoutPanel.
Then on a button click, I need to process the data entered by the user on those dynamically created controls. For that purpouse I use a Backgroundworker which is supposed to take those controls and read their data.
My issue es that the Backgroundworker doesn't allows me to access the control from the DoWork Method, but I need to do it that way because I'll be reporting the progress of the operations.
Here are portions of my code to clarify the concept:
private void frmMyForm_Load(object sender, EventArgs e)
{
//I add multiple controls, this one is just for example
LayoutPanel1.add(TextBox1);
....
}
private void bgwBackground_DoWork(object sender, DoWorkEventArgs e)
{
foreach (Control controlOut in LayoutPanel1.Controls)
{
//do some stuff, this one is just for example
string myString = controlOut.Name; //-> Here is the error, cant access controls from different Thread.
}
}
Setting text is simple just using a delegate, but how about getting the entire parent control to manipulate the child controls (just for getting info, I don't want to set any data, just need to Get Name, Text, stuff like that).
Hope I made myself clear, thank you all.
You can only access Windows Forms controls from the GUI thread. If you want to manipulate them from another thread, you will need to use the Control.Invoke method to pass in a delegate to execute on the GUI thread. In your situation, you should be able to do this:
private void bgwBackground_DoWork(object sender, DoWorkEventArgs e)
{
foreach (Control controlOut in LayoutPanel1.Controls)
{
this.Invoke(new MethodInvoker(delegate {
// Execute the following code on the GUI thread.
string myString = controlOut.Name;
}));
}
}
I like to define an extension method that allows me to use the cleaner lambda syntax:
// Extension method.
internal static void Invoke(this Control control, Action action) {
control.Invoke(action);
}
// Code elsewhere.
this.Invoke(() => {
string myString = controlOut.Name;
});
As you are already aware, accessing control values from any thread other than the UI thread is a big no-no. I'd say one reasonable implementation is to use a .NET synchronization mechanism, such as a WaitHandle, to suspend your background thread while the UI thread updates a thread-safe data structure of your choice.
The idea is that your background thread notifies the UI thread (via the delegate mechanism you are already familiar with) that it needs information, then waits. When the UI is finished populating the shared variable with information, it resets the WaitHandle, and the background worker resumes.
Without writing out and testing all the code, let me give you a few resources:
WaitHandle.WaitOne documentation with example usage: http://msdn.microsoft.com/en-us/library/kzy257t0.aspx
My own favorite method of invoking an event on the UI thread: http://www.notesoncode.com/articles/2009/01/24/PowerfulExtensionMethodsPart1.aspx
I got my hands om MEF for a week now and I am trying to build up a WPF application that loads imported controls from MEF.
I created a WPF application project and removed the default window and application start up URI. Then I handled the application startup event to compose the application:
public partial class App : Application, IPartImportsSatisfiedNotification
{
{...}
private void App_Startup(object sender, StartupEventArgs e)
{
this.Compose();
}
public void Compose()
{
try
{
globalCatalog.Catalogs.Add(new DirectoryCatalog(extensionsDirectoryPath));
CompositionContainer container = new CompositionContainer(globalCatalog);
container.ComposeParts(this);
}
catch (Exception ex)
{
// Do something
}
}
{...}
}
Actually, when debugging and watching objects after imports are satisfied, everything has hierarchically composed fine like I wanted. But when I try to show up the MainWindow of the application an exception is thrown on MainWindow.Show() call:
"Specified element is already the logical child of another element. Disconnect it first."
Though my code in OnImportsSatisfied method seems fine as it is working when not using MEF mecanism:
public void OnImportsSatisfied()
{
Window mainWindow = new Window();
mainWindow.Content = this.importedControl;
this.MainWindow = mainWindow;
this.MainWindow.Show();
}
I insist on the fact that this works perfectly when not importing controls with MEF. What is surprising is that this code does not work too:
Window mainWindow = new Window();
//mainWindow.Content = this.importedControl;
this.MainWindow = mainWindow;
this.MainWindow.Show();
So I suspect that ComposeParts is doing a bit more than what it says as it is the only member acting on my actual application instance.
Hope someone can help me (Glenn?).
Thanks.
Edit:
I discovered that when I remove the IPartImportsSatisfiedNotification interface from my parts, no exception is thrown and the window shows up. But of course the window is empty as I need this OnImportsSatisfied method to set the DataContext of the window to its associated imported view model.
The sample applications of the WPF Application Framework (WAF) show how to use MEF within a WPF application.
I finally discovered that I was importing my WPF user controls by using the default ImportAttribute constructor, which in fact will make a shared instance of the class if the creation policy is not specified during export. And as many of my controls were implementing the same interface and I was binding them in my views, I was actually trying to add this shared user control instance to different visual elements, which is not permited by WPF (and so the exception).
I marked my imports using the RequiredCreationPolicy set to NonShared and everything got back in order! That was all about learning MEF...
I think this is a bug in the WPF framework, without going into depths of my program and why I am doing what I am doing, I wrote a simple test application to prove my theory.
Can this issue be confirmed? What are possible workarounds for a series of dialogs to be executed before putting the application into its run loop?
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace ShowDialogWindow
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
Window testWindow = new Window();
testWindow.ShowDialog();
testWindow.Close();
// THE CODE BELOW WILL NOT SHOW THE NEXT WINDOW
Window testWindow2 = new Window();
testWindow2.ShowDialog();
testWindow2.Close();
}
}
}
Update:
Sure I can change my design to accommodate this behaviour. What I was trying to do was really simple however.
I have derived my MyApplication from Application. On the Main(), I initiate a series of start checks, for example, license information, splash screen, connectivity and configuration checks, ect. When I get my all clear, I call MyApplicatiom.Run(MyMainForm).
This design is based on a normal Windows application which works without a problem.
Thanks a lot for your help, and I will remember that you cannot call ShowDialog() before application.Run() as it simply innitializes a shutdown. I would have thought however that a shutdown sequence should only be initiated after a App.Run() instruction.
Please correct me if I am understanding this wrong.
Not a bug. The default ShutdownMode of Application is OnLastWindowClosed, so as soon as the first window is closed your application will start shutting down! Change to OnExplicitShutdown and it will work, but you'll have to manage the shutdown.
I think you probably just need to rethink what you're actually trying to achieve. Why would you display two subsequent dialog windows during the startup of your app?
For anyone that has the same problem, here is how you can get around it:
public App()
{
// Preserve and temporarily switch shutdown mode
var oldShutdownMode = ShutdownMode;
ShutdownMode = ShutdownMode.OnExplicitShutdown;
var dialog = new Window();
var result = dialog.ShowDialog();
dialog = new Window();
result = dialog.ShowDialog(); // This will show!
// Reset shutdown mode to original value
ShutdownMode = oldShutdownMode;
}