Create complex UserControl during runtime without freezing the GUI - c#

I have an very complex UserControl which needs to be created at runtime. This creation freezes the GUI for about 5 seconds (which is not acceptable).
I tried to move this operation into an Background Worker and end up with this Exception:
The calling thread must be STA, because many UI components require this.
I'm aware that i can't use an MTA thread to create an UserControl / UI Element. I tried to use a combination of BackgroundWorker and Dispatcher but it didn't work.
First try
private void LetsGo()
{
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DispatcherOperation dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(new Action(this.GenerateControlAsync), DispatcherPriority.Background);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Cancelled
}
else if (e.Error != null)
{
//Exception Thrown
}
else
{
//Completed
}
}
private void GenerateControlAsync () {
this.Control = new TimeConsumingUserControl();
}
The code above is not working, the method this.GenerateControlAsync isn't executed.
Second try
private async void GenerateControl()
{
this.Control = await Dispatcher.CurrentDispatcher.InvokeAsync<UserControl>(this.GenerateControlAsync);
}
private UserControl GenerateControlAsync()
{
return new TimeConsumingUserControl();
}
this sample is working but it keeps freezing the GUI thread.
I'm using WPF and .net Framework 4.5.
Note that the method GenerateControlAsync() does not simply create an instance of an UserControl, there is a lot more logic involved.
To answer #HighCore's question:
In fact the XAML Code of the UserControl is transformed out of xml files via XSLT and the Codebehind is generated using CodeDOM. The whole things needs to be compiled and wrapped into an assembly. I use assembly.CreateInstance() to get an instance of the UserControl. This line throws the quoted exception. In my GUI i have a ContentControl which has a binding to the UserControl in my ViewModel. The data for the generation like the xml files which need to be transformed are retrieved from a webservice.
This is why the execution of this method takes a bit longer than someone might expect.

From your description of all the steps involved in creating your control it looks like you're lumping together a lot of work that doesn't need to be done on the same thread and trying to do it all on either the UI or background thread. You should instead be doing the minimum amount of work necessary on the UI thread (the actual UserControl instantiation) and doing everything else on a worker thread. Rather than a single async unit of work you should be doing 2 steps, which with async-await is very simple. It should look more like this:
var dynamicAssembly = await this.GenerateControlAssemblyAsync();
this.Control = this.GenerateControlFromAssembly(dynamicAssembly);
Because of the way await works the second line will automatically be run back on the original (UI) thread so no need for any Dispatcher calls. In GenerateControlAssemblyAsync you should use a Task.Run and do all of the other code in that. GenerateControlFromAssembly should be doing nothing but instantiating the UC instance.

You need to chunk your Assembly generation. Your assembly generation takes too much time and you need to put everything in another thread, and only the DIRECT Ui component generation in same thread.
There is cool method that can load Xaml from stream and do it in chunks, without chogging up UI. Have a look: http://msdn.microsoft.com/en-us/library/aa346591.aspx

Related

Background worker does not Work WPF

In my WPF program it took huge processing time and freezing for long time.
so I decided to use background worker and process it in background.
but it does not work. through debug, the program stop at Render3D(). It does not throw exception. Its like when you put return.
In other word it does nothing after reaching Render3D() and will just return.
(I don't say it will return Because im not sure but the behavior is same as return)
private readonly BackgroundWorker backgroundWorker = new BackgroundWorker();
private AssetDeclaration _assetDeclaration = new AssetDeclaration();
public MainWindow()
{
backgroundWorker.DoWork += backgroundWorker1_DoWork;
backgroundWorker.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 1000; i++)
{
if (!((BackgroundWorker)sender).CancellationPending)
{
Render3D(); // will return at this point. (why?) or waiting for something to start?
((BackgroundWorker)sender).ReportProgress(i);
}
else
{
e.Cancel = true;
break;
}
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Done!");//will show message box instant.
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBar1.Value = e.ProgressPercentage;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//...Some work here before starting Hard job!
//...From now i want to start heavy process in background.
//...with report to progress bar at same time.
backgroundWorker.RunWorkerAsync(100);
}
Render3D() works fine without Background processing but will freeze for some time.
Render3D() is in Partial class of MainWindow .because there are lots of methods so i decided to separate them.
Also how can I use ReportProgress outside backgroundWorker1_DoWork . for example in Render3D().
Last thing : i want to know how to show the user how much of process is done.
Solved!:
The problem was because i set Viewport3D inside Render3D()
I separated it from Render3D and problem got fixed. thanks to Henk Holterman for the right answer.
It seems some tasks cant be done in another Thread. with the Error report i find out that the invalid task is setting Viewport3D properties.
this tasks must be done in Main thread.
below is invalid Code that made background worker stop functioning.
DefineCamera();
Viewport.Children.Add(model); // Must be run in Main thread.
And this Part.
private void DefineCamera()
{
PerspectiveCamera camera = new PerspectiveCamera
{
FieldOfView = 60
};
PositionCamera(camera);
Viewport.Camera = camera; // Must be run in Main thread.
}
First of all, you had trouble finding the error.
... the program stop at Render3D(). It does not throw exception. Its like when you put return.
What actually happened was that an exception was thrown by your method and was captured by the Backgroundworker. It is transferred to the Completed event but you do have to act on it there.
private void worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
// check error, check cancel, then use result
if (e.Error != null)
{
// handle the error
}
else if (e.Cancelled)
{
// handle cancellation
}
else
{
// use the result(s) on the UI thread
}
// general cleanup
}
Failing to look at either e.Error or e.Result is the same as having an empty catch{} block in your program.
And with error handling in place we then have
oh yes it shown Error. System.InvalidOperationException the calling thread cannot access this object because a different thread owns it
This indicates that your Render3D() still interacts with the GUI somewhere.
The basic advice is to separate all the calculation (and I/O, database) work from the UI work. You can run the CPU bound and I/O bound cod in a thread but the GUI is single threaded, you can only interact with it from the main Thread.
In the world of WPF, unlike Windows Forms that you were used to, you should consider Dispatcher. To do this, you have to import System.Windows.Threading
private void ThreadTask()
{
Thread.Sleep(TimeSpan.FromSeconds(5));
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate()
{
//Do some heavy task here...
});
}
Quick Update
In order to run the thread from a button click or whatever, any function, add this line of code:
Thread thread = new Thread(new ThreadStart(ThreadTask));
thread.Start();
This line of code is equivalent to BackgroundWorker.RunWorkerAsync();
I would highly recommend using async/await. This feature was introduced in .NET 4.5, and is used to shift work off the main WPF GUI thread to make the application fast and responsive.
Essentially, the rule is to push any calculations which do not interact with GUI onto a background thread using a combination of Task.Run and async/await. Together with Dispatcher.Invoke, you don't really need anything else.
For example, a slow data call that might fetch data from the database could be pushed onto a background thread, so the application does freeze while it waits for the SQL to execute.
I've used this to make the applications that I write fast, responsive and snappy.

Window closing not working because of a race condition

Here's my code:
private void OpenLoadingWindow()
{
loadingWindow = new LoadingView();
loadingWindow.Closed += new EventHandler(LoadingWindow_Closed);
_go = true;
loadingWindow.ShowDialog();
}
public void OpenLoadingWindowInNewThread()
{
thread = new Thread(x => OpenLoadingWindow());
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
lock (_locker)
{
Monitor.Pulse(_locker);
}
}
public void CloseLoadingWindow()
{
lock (_locker)
while (!_go)
Monitor.Wait (_locker);
if (loadingWindow != null)
{
loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{
_go = false;
loadingWindow.Close();
loadingWindow = null;
}));
}
}
In code I first call OpenLoadingWindowInNewThread() and after that I call CloseLoadingWindow(). However, the first time the code is executed it works fine. But after that, the code in CloseLoadingWindow(), in BeginInvoke doesnt get executed. What am I doing wrong?
What I want to achieve is this: open the loading window, execute some code. After the code is execited I call the closing method, I want to close the loading window.
The main problem here is that you are creating a second thread for UI. Don't do that.
Unfortunately, you didn't provide a good code example. So for the sake of an answer, let's assume you're doing something like this:
void button1_Click(object sender, EventArgs e)
{
DoLoadingWork();
}
void DoLoadingWork()
{
OpenLoadingWindowInNewThread();
LoadingWork();
CloseLoadingWindow();
}
I.e. some event happened in your UI, and now you have to do some work. You implemented this by calling the methods you've shown in your question, processing the work in your UI thread and creating a second thread to show the dialog.
This is the wrong way to approach this. Instead, you should keep all of your UI in the same thread, and do the work in a different thread. That would look more like this:
void DoLoadingWork()
{
using (LoadingView form = new LoadingView())
{
form.Shown += async (sender, e) =>
{
await Task.Run(() => LoadingWork());
form.Close();
};
form.ShowDialog();
}
}
This version does the following:
Creates your status dialog in the UI thread
Subscribes to the Shown event, to ensure the dialog is visible before anything else happens
Shows the dialog
Once the dialog is shown, a new thread is started to execute the LoadingWork() method
When the LoadingWork() method completes, the dialog is closed, allowing the dialog to be disposed and the DoLoadingWork() method to return.
Note that even if you have to interact with the UI from the code that does the processing, or if you need a way to interrupt the processing, the above is still the correct way to do things. Those other aspects of the requirements can easily be implemented, using standard idioms for dealing with them.
Without an actual example of what that processing might be, and how the UI interaction and/or interruption works, it's impossible to say exactly how that part would be implemented. But it would generally involve using Invoke() for UI interaction (or even better, refactoring the processing so that it uses async/await, with UI interaction occurring between await statements for the individual pieces of the work) and a flag or CancellationToken to deal with interrupting the thread.
If your processing does in fact interact with the UI, and you did in fact run it in the UI thread, then it's likely you've got calls to methods like Refresh() or Application.DoEvents() interspersed. These methods are practically never required, and IMHO are always a sign that the code has been implemented incorrectly. As an added benefit of changing your implementation to put the right code in the right thread, you won't have to use any of those methods to interact with the UI (instead, you'll use Invoke()).

File Dialog from a Background Worker

While maintaining some code, I discovered that we have an infinite hang-up in a background worker. The worker requires access to a script file. The original code was written to pop up a file dialog if no script file was defined, to allow the user to select one. It looks something like this:
private void bgworker_DoWork(object sender, DoWorkEventArgs e)
{
... snip ...
if (String.IsNullOrWhitespace(scriptFile))
{
scriptFile = PromptForScript();
}
... snip ...
}
private string PrompForScript()
{
string script = "";
OpenFileDialog openDialog = new OpenFileDialog();
if (openDialog.ShowDialog() == DialogResult.OK)
{
script = openDialog.FileName;
}
return script;
}
I've read up a bit about MethodInvoker, but almost all of the invoke methods require that you call them from a control. The background worker in question is running from a separate class, which doesn't extend Control. Do I use the form that calls the class with the bgworker for that? Or is there another way of interrupting the thread for user input?
It's not recommended to invoke the UI from the background worker DoWork event handler. BackgroundWorker is meant to do work on a non-UI thread to keep the UI responsive. You should ask for any file information before starting the BackgroundWorker object with RunWorkerAsync.
What you want to do is capture the SynchronizationContext on the UI thread and pass that along to the background worker. The BackgroundWorker can call Send() (synchronous, like Invoke) and Post() (asynchronous, like BeginInvoke) on the context to invoke back to the correct UI thread. That said, there is probably no need for the BackgroundWorker in this case- a regular threadpool thread would do just fine.
This (slightly modified) block of code from http://msmvps.com/blogs/manoj/archive/2005/11/03/74120.aspx should give you the general idea:
private void button1_Click(object sender, EventArgs e)
{
// Here we are on the UI thread, so SynchronizationContext.Current
// is going to be a WindowsFormsSynchronizationContext that Invokes properly
ctx = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(
// This delegate is going to be invoked on a background thread
s => {
// This uses the context captured above to invoke
// back to the UI without the "messy" referencing
// of a particular form
ctx.Send(s2 =>
{
// Interact with your UI here- you are on the UI thread
},null);
}
);
}
If some Form kicks off a long-running process within another class that uses a BGworker, why wouldn't the form (or presenter, depending on UI architecture) handle the processing of the error state?
Perhaps, just pass back some status result (or throw a very targeted, specific exception that you can handle in the UI)?
Leave the background worker to determine if there IS an error, but leave handing the error (especially the UI portion of showing a message box) to the upper layers.
Sorry this didn't have more concrete code but it could go a lot of different ways depending on how your system is architected.
Well, the Form class has an Invoke method, so passing the form instance to the background working class should work.

c# calling backgroundWorker from another thread than UI thread

I'm trying to load loadingForm like below code. But it doesn't work, the loadingForm doesn't disappear, the event RunWorkerCompleted doesn't get called.
And also, I need to call loadingForm and backgroundWorker multiple times, so how do I completely dispose the loadingForm and the backgroundWorker after each call?
I think that there're many things wrong in my code but I don't know how to fix it. Could you show me how to solve my problem and point out where I need to fix? Thanks a lot in advance.
public partial class loginForm : Form
{
//....
private loadingForm lf;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
lf.Show();
While (backgroundWorker1.isBusy)
Application.DoEvents();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, DoWorkEventArgs e)
{
lf.Close();
}
private void connect()
{
//....
Thread mainThread = new Thread(ThreadStart(listentoServer));
mainThread.Start();
}
private void listentoServer()
{
//....
lf = new loadingForm();
backgroundWorker1.RunWorkerAsync();
//....
backgroundWorker1.CancelAsync();
//....
}
}
There's a lot of things wrong with your code. If you can, try to take a step back and describe what exactly you want to do.
BackgroundWorker uses the Event-based Asynchronous Pattern (EAP). As such, it requires a thread context in which to live. UI threads satisfy this requirement, but manually-created Thread instances do not (unless you install one or make the instance a secondary UI thread).
Similarly, UI components bind to a particular thread. They require an STA thread that does message pumping (e.g., Application.DoEvents).
It looks to me like you're creating a manual Thread and then creating UI components from that thread (so you know that the thread should be STA and include a message pumping loop, neither of which are in your code). Then that thread starts a BGW which does message pumping.
It's not clear what you're trying to accomplish here - maybe displaying a dialog in a separate thread?
Multiple UI threads in a WinForms app is not an officially supported scenario AFAIK, though some people have gotten it working. I've never seen a need for it, though.
According to what you have shown (which is admittedly incomplete, so this may not be the problem), you are not hooking up your event to the backgroundWorker_DoWork and backgroundWorker_RunWorkerCompleted event handlers. Somewhere (after you instantiate your backgroundWorker), you should have this:
backgroundWorker.DoWork += new EventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new EventHandler(backgroundWorker_RunWorkerCompleted);
As a disclaimer, this was written by hand, so the event names or EventHandler types may be incorrect.
i really don't know how to fix your code definitively, or if your code even works the way you have it, i can only give you the following guidance.
use CancellationPending property of background worker, not the IsBusy property
when working with windows forms and threaded code, always use the Invoke/BeginInvoke methods to make sure you marshal your call back to the thread that the control originated from.

C# How does a background thread tell a UI thread that it has finished doing something?

Scenario
Lets say you have a C# WinForms application that doing some data processing.
You have a method that retrieves data from a database that is called by the UI thread.
The background thread then runs off to do this task.
You want the UI to carry on doing its thing and not be locked up and unresponsive.
QUESTION
How do you let the background thread run off and do its processing and then automatically alert the UI thread when it has returned the results?
If you don't use a background worker thread (for whatever reason) then you must fire an event from your thread which is handled by the UI thread. For example I have this code that scans my mp3s and fires and event for each album found and then another event when it finished (or is stopped):
public void Build()
{
FindAlbums(Root);
// Final update
if (Library_Finished != null)
{
Library_Finished(this, null);
}
}
private void FindAlbums(string root)
{
// Find all the albums
string[] folders = Directory.GetDirectories(root);
foreach (string folder in folders)
{
string[] files = Directory.GetFiles(folder, "*.mp3");
if (files.Length > 0)
{
// Add to library - use first file as being representative of the whole album
var info = new AlbumInfo(files[0]);
if (Library_AlbumAdded != null)
{
Library_AlbumAdded(this, new AlbumInfoEventArgs(info));
}
}
FindAlbums(folder);
}
}
Then in the UI thread (this is WinForms code):
private void Library_AlbumAdded(object sender, AlbumInfoEventArgs e)
{
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { AddToGrid(e.AlbumInfo); });
}
else
{
AddToGrid(e.AlbumInfo);
}
}
private void Library_Finished(object sender, EventArgs e)
{
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { FinalUpdate(); });
}
else
{
FinalUpdate();
}
}
I would, however, recommend that you investigate the background worker thread, as it does so much of the housekeeping for you. However, the same handling code would be needed in the RunWorkerCompleted event to update the UI.
There are several ways of doing this, but the easiest way is to use a BackgroundWorker.
Essentially it has two delegates, the DoWork and the WorkCompleted. DoWork executes on a seperate thread and the WorkCompleted callback happens on the UI thread.
Here's more info:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
You can use the BackgroundWorker to do your time-intensive processing in its DoWork event handler. Then handle the RunWorkerComplete event -- it will fire when the DoWork method is finished. While all this is going on, your UI thread will be happily running along.
If you're using .NET 2.0 or newer, then this is made much easier with the BackgroundWorker thread. It has its own RunWorkerCompleted event that does just what you need.
I would highly recommend the BackgroundWorker in fact. It has the functionality most developers are after when creating threads. They're also easier to cancel gracefully, and they even have the ability to report progress.
Try to use BackgrounWorker and register a handler to the its RunWorkerCompleted event.
In Winforms you can use the .Invoke method (and check the .InvokeRequired property) to marshall a call back to the UI thread. You don't so much notify the UI thread - it keeps going on and doesn't wait for any sort of a completion, but you can interact with a control (for example, update the text property of a label) from another thread using the Invoke method.
You can also use the BackgroundWorker object (read MSDN to find out more about it), which implements a callback functionality to run some code on the UI thread after the background work is completed.
If you are talking about a WinForm app, you can make changes to any UI objects using the Invoke method on your form (or any of the controls on the form). You can also find useful the InvokeRequired property
You can store a reference to the UI thread Dispatcher by using Dispatcher.CurrentDispatcher (obviously in a method called by GUI thread). Using this object you can use the BeginInvoke or Invoke methods in your working thread to execute a method on the GUI thread notifying it that you have completed work. Personally I find this method to be slightly more flexible than using a background worker object and can produce slightly more readable code.
There's an easy way of working with multiple threads in C#. It is called BackgroundWorker.
You should check it out: BackgroundWorker Tutorial
As was mentioned many times, the BackgroundWorker class can be used.
Alternatively, you could do something akin to the following:
void buttonGo_Clicked( object sender, EventArgs e )
{
MyAsyncClass class = new MyAsyncClass();
class.LongOperationFinished += (LongOperationFinishedEventHandler)finished;
class.BeginLongOperation();
}
void finished( object sender, EventArgs e )
{
if( this.InvokeRequired ) {
this.BeginInvoke( (LongOperationFinishedEventHandler)finished, sender, e );
return;
}
// You can safely modify the gui here.
}

Categories