Reputation: 4882
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.
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.
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.
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.
Upvotes: 2
Views: 1805
Reputation: 26268
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
Upvotes: 1
Reputation: 24453
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.
Upvotes: 1