I´m quite new to WPF and to threading in WPF and have successfully implemented the bing maps control in my program. Problem is, the control takes a long time to load and slows down the program itself when opening it. The loading time has increased from about 2sec to about 20sec, which is not really acceptable.
My idea was, to load the bing maps control in a separate thread and thus not to decrease the performance. I´ve been trying to do that, but my UI keeps getting blocked by the loading process of the maps control.
Here´s an example using a Dispatcher:
private init()
{
Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate()
{
// Background thread: I would like to load the map here
Map map = new Map();
map.CredentialsProvider = providerKey;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
// Back to the UI thread
// How do I assign the loaded Map to this thread?
mapElementOnUI = map;
mapPanel.Children.Add(mapElementOnUI);
}));
}
));
thread.Start();
//Load the rest of the GUI here
}
If I handle the Threads like that I get an InvalidOperationException (no STA-Thread). If I change the code to the following, my UI blocks while loading the maps control:
private init()
{
Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate()
{
// Background thread
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
// Back to the UI thread
mapElementOnUI = new Map();
mapElementOnUI.CredentialsProvider = providerKey;
mapPanel.Children.Add(mapElementOnUI);
}));
}
));
thread.Start();
//Load the rest of the GUI here
}
I´ve also been trying to implement a solution via await and async without success. Is it possible at all to load the maps control in a separate thread? Could somebody help me out on this with a piece of code? Thanks a lot!
Well, in your second example all you're doing is starting up another thread that immediately switches back to the UI thread to create a control, so that's why your UI is blocking.
As for your first example, you can't create a control on one thread and use it on another (that's not the exception you were getting. You need to call SetApartmentState before calling Start). So you can't create the map control on a background thread and then load it into your window created on the main thread. This is prevented by the framework.
What you could do is create a separate window on its own UI thread and load the map control into that window. This would prevent your main application window from blocking, but it would require you to manage a second application window. Also, if you wanted objects in the main application thread and objects in the map control thread to interact with each other, you'd have to do a bunch of extra work handling cross-thread calls on both ends. You can read about this approach here.
As for the map control itself, I'm not familiar with it so I don't know if there's some other way to handle or defer loading without freezing your UI.
Ok, I´ve solved this now. #user1100269 gave a good hint resolving the exception I was experiencing: thread.SetApartmentState.STA. Additionally I had to create a new Map instance in the background thread without it beeing used anywhere - I didn´t really get that, but my problem is solved now. The map is loaded in the background and doesn´t block the UI anymore. Here´s the working code for anyone interested:
private void init()
{
Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate()
{
// Background Thread
Map map = new Map();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
// Back to the UI Thread
var provider = new ApplicationIdCredentialsProvider(MyCredentials);
mapElementOnUI = new Map();
mapElementOnUI.CredentialsProvider = provider;
mapPanel.Children.Add(mapElementOnUI);
updateMapLocations();
}));
}
));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
Related
I have a COM object with a GUI-component, lets call it "FileViewer". This FileViewer component can load a file by using a method, lets call it "LoadFile", that needs to use the UI thread to work. To use LoadFile, the FileViewer have to already been added and loaded into the GUI.
The problem is that the files that you load with LoadFile can be really big which causes the application UI to freeze for multiple seconds, making for a terrible user experience.
My quesion is: Is there anyway to call this LoadFile async without locking the UI thread even thought it requiring a UI thread to load.
What I've tried:
//My standard approach of doing things async
public async btnPressed (){
await new TaskFactory().StartNew(() => {
myFileViewer.LoadFile("C:\..."); //This still freezes the UI thread
});
}
//Maybe could prevent locking if it's not visible
myFileViewer.Parent = null;
myFileViewer.Visible = false;
myFileViewer.LoadFile("C:\..."); //This still freezes the UI
myFileViewer.Parent = this;
myFileViewer.Visible = true;
//Maybe could create the viewer in a new window "TestProxy" with a new "UI thread", load the file and then insert the viewer back into my first window
Thread thread = new Thread(() => {
new TestProxy().Show();
TestProxy.CreateViewerAndLoadFile("C:\..."); //Does actually not freeze my application
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
//this version actually worked, but the problem is that there seem to be no way to move the viewer back into my original window after loading it into this new one
I'm very sure that the Viewer class in itself does not have any support for doing LoadFile async, so I am more interested in a general solution of the problem. Just dosn't feel like it should be something that's impossible to do.
PS:
The name of the GUI component is "AxInventorViewControl" which I create an instance of and "LoadFile" is infact a row of code axInventorViewControl1.FileName = filePath;
I need to create window with loading gif when my main window is rendering. I have read some articles and make a decision that for this purposes i need to create new thread. I did it like in this article
As a result I have something like that:
LoadingDialog _loadingDlg;
Thread loadingThread;
public void ShowLoading()
{
loadingThread = new Thread(new ThreadStart(loadingThreadWork));
loadingThread.SetApartmentState(ApartmentState.STA);
loadingThread.Start();
}
private void loadingThreadWork()
{
_loadingDlg = new LoadingDialog();
_loadingDlg.Show();
System.Windows.Threading.Dispatcher.Run();
}
public void HideLoading()
{
_loadingDlg.Dispatcher.InvokeShutdown();
}
First time when I call ShowLoading() and then HideLoading() everything works like I want. But when I call ShowLoading() at the second time I get an exception at
_loadingDlg.Show();
with message The calling thread cannot access this object because a different thread owns it.
How can this be? _loadingDlg is created in the previous line, and in the same thread.
In the loadingThreadWork you're creating the control, before the first run it's a null, so in first time you succeed. However, you're creating the dialog in a different thread, which is marked as an owner for the control.
At the next time you're calling the loadingThreadWork, control isn't null, and ant change for it from a different thread (and it is a different thread, because you're creating it again) leads to the exception you've got.
As you're using WPF, you probably should switch from threads to async operations, which are much more readable, supportable and predictable than your current solution.
I'm working in WPF, C#, VisualStudio 2013, and I'm stucking in this problem. I'll explain in steps:
1- From the main thread (Windows1) I'd like to display a secondary windows (Windows2). So, I call to my function of thread:
... CreatethreadWindow(...);
To solve this problem I've searched a lot and found the answer, no problem with this point. The code is below (Reference to this code):
void threadWindow1(...)
{
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
// Create our context, and install it:
SynchronizationContext.SetSynchronizationContext(
new System.Windows.Threading.DispatcherSynchronizationContext(
System.Windows.Threading.Dispatcher.CurrentDispatcher));
// Create and show the Window
Windows2= new WindowsPredefine(param1, param2,..., paramN);
// Create an event handler to communicate events from windows 2 to the main windows.
Windows2.Windows2EventHandler += new RoutedEventHandler(NewDataFromWindows2EventHandler);
Windows2.Closed += (sender1, e1) => Windows2.Dispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Background);
Windows2.Show();
// Start the Dispatcher Processing
System.Windows.Threading.Dispatcher.Run();
}));
// Set the apartment state
newWindowThread.SetApartmentState(ApartmentState.STA);
// Make the thread a background thread
newWindowThread.IsBackground = true;
// Start the thread
newWindowThread.Start();
}
Notice the line Windows2= new WindowsPredefine(param1, param2,..., paramN); where my second windows in created in a background thread.
2- I'd like to establish a communication between Windows2 and Windows1. So I made this by creating and RoutedeventHandler. So, Windows2 can communicate to Windows1 when it's finished its tasks. The problem arises in step 3.
3- Now, as Windows2 has already finished its tasks, from Windows1 we want to start again all the process in Windows2 (without closing it), but just changing some of the values of the parameters when we called to the constructor WindowsPredefine(param1, param2,..., paramN);
I've tried a couple of things, but any of them works... For example, I've defined Windows2 as global variable, and I've tried to call to the constructor (I've made first public access) from the mainThread, but it don't allow me to re-call a function that is running in a thread from the mainWindow (Windows1).
How can I approach this problem? Thank you for your help!
I read it's not possible to access UI elements from another thread (not only GUI, but all 2 different threads) and saw a code how to workaround it, but didn't find an explanation why can't I do it?
The reason MSDN gives for not allowing this:
Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible as well, including race conditions and deadlocks. It is important to ensure that access to your controls is done in a thread-safe way.
So technically I could be possible (and maybe they left it out by-design), but the problem is the underlying code from Microsoft isn't thread safe. This has probably to do with the inner workings of Windows and it's lack of thread safety in their Win32 UI model.
You cant make changes to the GUI from any other thread but the GUI thread(main thread). That being said you can read the data from controls (textboxes etc) from whatever thread without problems if you want to make changes to controls from another thread you need to use something like this:
textBox1.Invoke((MethodInvoker)(() =>
{
textBox1.Text = "text changed from another thread";
}));
The downside of this is that now the thread from which you are calling this is getting blocked until this change to the textbox is done. This isnt a problem if you are updating your controls from your thread not often but if you want to do it often you need to create a 3rd thread that handles the updates depending on what the worker thread is doing. Something like this
List<string> list = new List<string>();
Thread workThread = new Thread(dowork =>
{
//random work
for(int a = 0; a< 1000000;a++)
{
list.Add(a+" iteration");
Thread.Sleep(10);
}
});
workThread .Start();
bool updateThreadWorking = true;
Thread updateThread = new Thread(dowork =>
{
while(updateThreadWorking)
{
while(list.Count > 0)
{
listBox.Invoke((MethodInvoker)(() =>
listBox.Items.Add(list[0]);
}));
list.RemoveAt(0);
}
Thread.Sleep(200);
}
});
updateThread .Start();
Now the workthread is working at full speed without getting blocked by the UI and the UI is still getting updated on the current status
In my wpf application I have added a piece of code in the button click as below:
private void btn_convert_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(new ThreadStart(WorkerMethod));
t.SetApartmentState(ApartmentState.MTA);
t.IsBackground = true;
t.Start();
}
Inside my WorkerMethod() method I have some code like the line below:
btn_convert.Content = "Convert";
When it reaches to this line it throws the exception as the calling thread cannot access this object because a different thread owns it.
I dont want to use Dispatcher as it freezes the UI.. UI should be responsive so I have not opted for Dispatcher invoke or BeginInvoke.
Please give me your valuable suggestions.
I dont want to use Dispatcher as it freezes the UI.. UI should be responsive so i am not opted for Dispatcher invoke or BrginInvoke.
That just shows that you've used the dispatcher badly.
You must access the UI from the UI thread. That doesn't mean your whole WorkerMethod needs to run on the UI thread, but this line:
btn_convert.Content = "Convert";
definitely does. So you might want to keep your current code for starting a thread (do you really need to set the apartment state though) but change any code accessing the UI to use the dispatcher. For example:
Action setButtonContentAction = () => btn_convert.Content = "Convert";
Dispatcher.BeginInvoke(setButtonContentAction);
Alternatively, depending on what your WorkerThread is doing - and if you're using C# 5 - you might want to use the new async features. That can make it easier to keep UI work on the UI thread, but it does depend on what else is going on.
UI changes can only be applied by the main thread. You can check if the main thread call is necessary:
if (btn_convert.InvokeRequired)
{
btn_convert.Invoke((MethodInvoker)(() => btn_convert.Content = "Convert"));
}