Generating and passing complex content to the GUI thread in WPF/C# - c#

I'm aware, and use, the xxx.Dispatcher.Invoke() method to get the background thread to manipulate GUI elements. I think I'm bumping up against something similar, but slightly different, where I want a long running background task to construct a tree of objects and when done hand it to the GUI for display.
Attempting to do that results in an InvalidOperationException, "due to the calling thread cannot access this object because a different thread owns it." Curiously, this doesn't happen with simple type.
Here's some example code that demonstrates a trivial case the throws the exception. Any idea how to work around this? I'm pretty sure that the problem is the background thread owns the factory constructed object and that the foreground GUI thread can't take ownership, although it works for more simple system types.
private void button1_Click(object sender, RoutedEventArgs e)
{
// These two objects are created on the GUI thread
String abc = "ABC";
Paragraph p = new Paragraph();
BackgroundWorker bgw = new BackgroundWorker();
// These two variables are place holders to give scoping access
String def = null;
Run r = null;
// Initialize the place holders with objects created on the background thread
bgw.DoWork += (s1,e2) =>
{
def = "DEF";
r = new Run("blah");
};
// When the background is done, use the factory objects with the GUI
bgw.RunWorkerCompleted += (s2,e2) =>
{
abc = abc + def; // WORKS: I suspect there's a new object
Console.WriteLine(abc); // Console emits 'ABCDEF'
List<String> l = new List<String>(); // How about stuffing it in a container?
l.Add(def); // WORKS: l has a reference to def
// BUT THIS FAILS.
p.Inlines.Add(r); // Calling thread cannot access this object
};
bgw.RunWorkerAsync();
}
The grand scope of the problem is that I have a large document that I'm constructing on the fly in the background and would love for the GUI to show what's been generated so far without having to wait for completion.
How can a background worker act as an object factory and hand off content to the main thread?
Thanks!

You are trying to create the Run in the background thread, but Run is a FrameworkContentElement which inherits from DispatcherObject and thus is bound to the thread that created it.

As Franci said, Run is a DispatcherObject, so it is only updatable on the thread that created it. The code should run if it calls Dispatch.Invoke or Dispatcher.BeginInvoke like this:
private void button1_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
string abc = "ABC";
var p = new Paragraph();
var bgw = new BackgroundWorker();
String def = null;
Run r = null;
bgw.DoWork += (s1, e2) =>
{
def = "DEF";
button.Dispatcher.BeginInvoke(new Action(delegate{r = new Run("blah");}));
};
bgw.RunWorkerCompleted += (s2, e2) =>
{
abc = abc + def;
Console.WriteLine(abc);
var l = new List<String> { def };
p.Inlines.Add(r); // Calling thread can now access this object because
// it was created on the same thread that is updating it.
};
bgw.RunWorkerAsync();
}

Related

Creating a form that subscribes to events from a blocking thread

So I have to update a program to use a newer version of Awesomium, specifically 1.7.5
Well with the update Awesomium now has to operate on it's own thread, and it's blocking.
I can queue work to the blocking thread using WebCore.QueueWork() and this will complete the action passed on the thread WebCore.Run() was called. I made sure to give it it's own thread so the rest of my application isn't blocking.
The way the program used to function was by creating a worker object that had a constructor which instantiated a WebView and WebSession using the WebCore library. It then created a form which accepts a worker object as an argument which allows the form to subscribe to events from the WebCore library.
var worker = new Worker();
var debugForm = new PBForm(worker);
debugForm.Show();
The worker constructor has this line of code which calls the function SurfaceIsDirty whenever the view is updated.
((ImageSurface)_view.Surface).Updated += (s, e) => { if (webView_SurfaceIsDirty != null) webView_SurfaceIsDirty(s, e); };
This function is assigned in the form constructor:
this.worker.webView_SurfaceIsDirty = (sender, e) =>
{
ImageSurface buffer = (ImageSurface)this.worker._view.Surface;
pictureBox1.Image = buffer.Image;
};
So the form picture updates whenever the WebView is updated.
This used to be able to run in the WebCore thread but now since the WebCore thread is blocking I can't get this form to work properly on it.
So this is where I'm stuck. I need to run the Form in a separate thread so it doesn't just hang because it's stuck with the WebCore thread which is blocking.
My idea is as follows:
When a worker is created create a form in a new thread as a property of the worker instance.
When a WebCore event occurs the worker instance should be able to update it's Form.
It's compiling, the form is responsive, yet the picture is not updating and I suspect it's related to the form being in a different thread now. Here's the relevant code I have right now:
I added this property to the worker class:
public PBForm2 DebugForm;
I instantiate the worker class in the WebCore blocking thread:
WebCore.QueueWork(AddWorker);
In the AddWorker method I make a new thread and run a Form while attaching it to the worker property:
static void AddWorker()
{
var worker = new Worker();
Workers.Add(worker);
new Thread(() =>
{
worker.DebugForm = new PBForm2(worker.Id);
var debugForm = new PBForm2(worker.Id);
Application.Run(debugForm);
Application.DoEvents();
}).Start();
}
And finally the worker event itself is now:
((ImageSurface)_view.Surface).Updated += (s, e) =>
{
ImageSurface buffer = (ImageSurface)_view.Surface;
DebugForm.pictureBox1.Image = buffer.Image;
DebugForm.pictureBox1.Refresh();
};
It seems very close to working, the form responds to user interaction and the workers are doing their thing and triggering events, but the picture isn't changing in the form. The event is getting hit and the new image is there, I suspect the fact that the form is in a different thread is causing the image on the form to not update.
This was a very long post so if you are reading this thank you for taking the time to get through it all. I'm very much a novice when it comes to threading and any suggestions or links or even what exactly to search up to solve this issue would be greatly appreciated.
You are creating 2 of the same forms:
worker.DebugForm = new PBForm2(worker.Id);
var debugForm = new PBForm2(worker.Id);
then loading debugForm, but your updates are being done to DebugForm.picturebox1 so your updates will not be seen. Updates would need to be done to debugForm.picturebox1, but you should only have one created.
Without seeing all the code, why not just load the one in the worker class or point one to the other?
Application.Run(worker.DebugForm);
Application.DoEvents();
or
worker.DebugForm = new PBForm2(worker.Id);
var debugForm = worker.DebugForm;
Application.Run(debugForm);
Application.DoEvents();
I figured it out, after fixing the issue where I was updating the wrong Form object (thanks Troy Mac1ure) I ran into a threading issue where I couldn't access the Form's picturebox from the Awesomium thread.
I solved it using a helper class:
public static class ThreadHelper
{
private delegate void SetPictureCallback(PBForm f, Image image);
private delegate void AppendTextCallback(PBForm f, string text);
public static void SetPicture(PBForm form, Image image)
{
if (form.InvokeRequired)
{
SetPictureCallback d = SetPicture;
form.Invoke(d, form, image);
}
else
{
form.pictureBox1.Image = image;
form.pictureBox1.Refresh();
}
}
public static void AppendText(PBForm form, string text)
{
if (form.InvokeRequired)
{
AppendTextCallback d = AppendText;
form.Invoke(d, form, text);
}
else
{
form.textBox1.Text += text;
form.textBox1.SelectionStart = form.textBox1.TextLength - 1;
form.textBox1.ScrollToCaret();
form.textBox1.ScrollToCaret();
}
}
}
When the event is triggered in the worker thread I call the function to update the Form:
_view.Surface = new ImageSurface();
((ImageSurface)_view.Surface).Updated += (s, e) =>
{
ImageSurface buffer = (ImageSurface)_view.Surface;
ThreadHelper.SetPicture(DebugForm, buffer.Image);
Application.DoEvents();
};
_view.ConsoleMessage += (s, e) =>
ThreadHelper.AppendText(DebugForm, string.Format("{0} : {1} [{2}]\r\n", e.LineNumber, e.Message, e.Source));

About threads in c#

I have a button click which contains some ten methods. Here I want to use threads in the button click or some where in the code so that my windows form application will not hang.
This what I have tried so far...!!
collectListOfPTags();
REqDocCheck = new Thread(new ThreadStart(collectListOfPTags));
REqDocCheck.IsBackground = true;
REqDocCheck.Start();
WaitHandle[] AWait = new WaitHandle[] { new AutoResetEvent(false) };
while (REqDocCheck.IsAlive)
{
WaitHandle.WaitAny(AWait, 50, false);
System.Windows.Forms.Application.DoEvents();
}
In the method collectionListOfPtags() am getting an exception which says "a combobox is accessed from the thread other than it was created on"
Thank's for the patience..
Any help will be appreciated..
This looks like a good fit for the BackgroundWorker component.
Split your collectListOfPTags method into 2 methods - the first collects and processes the data and the second updates the UI controls.
Something like this:
void OnClick( ... )
{
var results = new List<string>();
var bw = new BackgroundWorker();
bw.DoWork += ( s, e ) => CollectData( results );
bw.RunWorkerCompleted += ( s, e ) => UpdateUI( results );
bw.RunWorkerAsync();
}
void CollectData( List<string> results )
{
... build strings and add them to the results list
}
void UpdateUI( List<string> results )
{
... update the ComboBox from the results
}
The BackgroundWorker will run CollectData in the background on a thread pool thread, but will run UpdateUI on the UI thread so you can access the ComboBox correctly.
What you need here is a delegate! You simply need to create a delegate and put it in the function which accesses GUI from thread function.
public delegate void DemoDelegate();
In your code,
collectionListOfPtags()
{
if ((this.InvokeRequired)) {
this.Invoke(new DemoDelegate(collectionListOfPtags));
return;
}
// Your Code HERE
}
I hope this will work! Good Luck :-)
You should take a look at threadpools? A thread pool is a collection of threads that can be used to perform several tasks in the background. They are simple of use and thread safe.
Here's an (really simple) example :
http://msdn.microsoft.com/en-us/library/h4732ks0.aspx

Calling code multi-able time using Threading?

Please see the following code:
foreach(string url in urls)
{
//Method that will process url ProcessUrl(url)
//Add eached proccessed url to a treelist
}
ProcessUrl method have HttpWebRequest and HttpWebResponse so sometime it takes a nudge and if there were many links it will take time which will hang my program.
I can't actually suggest a solution of think of one, because i may based it on something wrong so what i want is to make this code runs while i can operate 100% in my program without any crashes or hangs, and that each newly processed link will be inserted to the treelist without any lag.
If you want to perform a long-running operation in the background and pass the results of the operation back to the UI as they become available, while at the same time the UI stays responsive, then it's straightforward to use BackgroundWorker here.
void BeginExpensiveOperation()
{
var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += ExpensiveWork;
worker.ProgressChanged += WorkerOnProgressChanged;
List<string> urls = new List<string> { "http://google.com" };
worker.RunWorkerAsync(urls);
}
// runs in a worker thread
void ExpensiveWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
var urls = (List<string>) e.Argument;
foreach (var url in urls)
{
//TODO: do your work here synchronously
var result = new WebClient().DownloadString(url);
//TODO: pass the result in the userState argumetn of ReportProgress
worker.ReportProgress(0, result); // will raise worker.ProgressChanged on the UI thread
}
}
private void WorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
{
//this executes on the UI thread
var value = progressChangedEventArgs.UserState;
//TODO: use result of computation to add it to the UI
panel.Children.Add(new TextBlock {Text = value.ToString()});
}
Fill in your problem-specific code in the //TODO: placeholders and call BeginExpensiveOperation() to start the operation asynchronously.

Calling Invoke hangs program

Basically, this is what happens. I have a thread(endless loop) that runs as a background process while the form is showing. The thread checks if there is a need to add a new ToolStripMenuItem.
If the conditions are met, I'll need to use Invoke in order to create the UI object right? Problem with this is, when the this.Invoke or BeginInvoke is called, the form became unresponsive while the thread that does the checking is still running fine. Any ideas?
This is the first time i'm trying with this multithreading thingee. I'm sure i've missed out something.
public void ThreadSetCom()
{
while (true)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = p;
tsmi.Name = p;
tsmi.Click += new EventHandler(itm_Click);
if (this.msMenu.InvokeRequired)
{
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
}
else
{
cpDropdownList.DropDownItems.Add(tsmi);
}
}
}
}
Your ThreadSetCom method never exits:
while (true)
... with no return or break statements. That's going to hang the UI thread forever.
It's not clear what you're trying to achieve, but you definitely don't want to be looping like that in the UI thread. I'd argue that you don't want to be looping like that in a tight way in any thread, mind you...
I think a better approach for you would probably be to use a BackgroundWorker. I say that because what you're experiencing isn't that uncommon when doing multi-threading in a Windows Forms application. Further, the BackgroundWorker is able to manage the thread switching properly. Let me give you an example of that code with the BackgroundWorker.
Build a private class variable
private BackgroundWorker _worker;
Add to the CTOR
public {ctor}()
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.WorkerReportsProgress = true;
_worker.DoWork += new DoWorkEventHandler(BackgroundThreadWork);
_worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundThreadProgress);
}
DoWork handler
private void BackgroundThreadWork(object sender, DoWorkEventArgs e)
{
while (!_worker.CancellationPending)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
_worker.ReportProgress(1, p);
}
}
}
Report progress handler
private void BackgroundThreadProgress(object sender, ReportProgressEventArgs e)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = e.UserState as string;
tsmi.Name = e.UserState as string;
tsmi.Click += new EventHandler(itm_Click);
cpDropdownList.DropDownItems.Add(tsmi);
}
The Loop
However, one thing you're going to have to do is figure out how to get out of this loop. When should it exit? Whatever that means, you need to add to the if statement that exists there in my example because this loop will never end otherwise.
What the effect of this code snippet:
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
is that the method 'ThreadSetCom' will be invoked in the UI thread. And there is an infinitive loop in that method. That is why your form becomes unresponsive.
I suggest you that you should move the foreach clause to a separate method and invoke this method in the UI thread when the condition is hit, for example the diff.Count>0.

How is this not causing a cross thread exception in silverlight?

I am aware that you need to use a Dispatcher to update items in the UI thread from a worker thread. To confirm my understanding, when you get the Dispatcher associated with current object is it always the UI dispatcher if my class inherits from the UserControl class? In which cases is it not the UI dispatcher?
Anyway, in the following code, I am creating a query and starting it asynchronously and when it completes, it sets the itemsource on one of my UI elements. I also am adding items to an observable collection that a UI element uses as its itemsource. When this is ran, it works fine and isn't fussing at me to use a dispatcher and update the UI that way. Why is that?
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
QueryTask queryTask = new QueryTask(URL);
queryTask.ExecuteCompleted += new EventHandler<QueryEventArgs>(queryTask_ExecuteCompleted);
queryTask.Failed += new EventHandler<TaskFailedEventArgs>(queryTask_Failed);
Query query = new Query();
query.Where = "Field <> 'XXX'";
query.OutFields.Add("*");
queryTask.ExecuteAsync(query);
BuildingsOrganizationList.ItemsSource = organizationList;
}
void queryTask_ExecuteCompleted(object sender, QueryEventArgs e)
{
FeatureSet featureSet = e.FeatureSet;
foreach (KeyValuePair<string, string> columns in featureSet.FieldAliases)
{
TypeGrid.Columns.Add(new DataGridTextColumn()
{
Header = columns.Key,
Binding = new System.Windows.Data.Binding("Attributes[" + columns.Key + "]"),
CanUserSort = true
});
}
TypeGrid.ItemsSource = featureSet.Features;
TypeBusyIndicator.IsBusy = false;
testing();
}
private void testing()
{
List<string> temp = new List<string>();
temp.Add("Item 1");
temp.Add("Item 2");
temp.Add("Item 3");
foreach (string org in temp)
{
organizationList.Add(org);
}
}
Because even though the processing is done asynchronously, you retrieve the result in your UI thread (an event is NOT thread), and update it from there.
If, however, you put the code inside queryTask_ExecuteCompleted in a Task:
Task.Factory.StartNew(() =>
{
//code of queryTask_ExecuteCompleted here
});
You will get your exception.
The ExecuteCompleted event happens on the same thread that calls ExecuteAsync.

Categories