WinForms threading invoke handling - c#

This code is running from other thread than the thread it was created on.
Thread gets create from the constructor of StartScanning
public StartScanning()
{
InitializeComponent();
Thread _IMSS_THREAD = new Thread(_IMSS_START_SCANNING);
_IMSS_THREAD.IsBackground = true;
_IMSS_THREAD.Start();
}
Main form
StartScanning _IMSS_START_SCANNING = StartScanning._IMSS_CREATE_CONTROLE();
_IMSS_START_SCANNING._IMSS_ON_ALL_SCAN_COMPLETE += _IMSS_ON_SCAN_COMPLETE;
this._IMSS_MainPanel.Controls.Add(_IMSS_START_SCANNING);
On scan complete user control, this code is in main form:
ScanComplete _IMSS_ON_COMPLETE = new ScanComplete();
public void _IMSS_ON_SCAN_COMPLETE(ref List<BetterListViewGroup> _IMSS_LIST_OF_GROUP_TARGETS)
{
List<BetterListViewGroup> IMSS_LIST_OF_GROUP_TARGETS = _IMSS_LIST_OF_GROUP_TARGETS;
_IMSS_ON_COMPLETE._IMSS_AddRangeTargets(ref IMSS_LIST_OF_GROUP_TARGETS);
this.Invoke(new MethodInvoker(() =>
{
this._IMSS_MainPanel.Controls.Clear();
this._IMSS_MainPanel.Controls.Add(_IMSS_ON_COMPLETE);
}));
}
If you take a look on this code, it runs OK but it's supposed to throw
Cross-thread operation not valid, cause when we start the program this UserControl
ScanComplete _IMSS_ON_COMPLETE = new ScanComplete();
Get created on the main thread (it's global) and when we use
_IMSS_ON_COMPLETE._IMSS_AddRangeTargets(ref IMSS_LIST_OF_GROUP_TARGETS);
It adds a list of groups of listview to it, and it's out of the invoke section, but it's not throwing thread error, Why it's not throwing errors?

Try this in constructor of form:
public StartScanning()
{
InitializeComponent();
StartScanning.CheckForIllegalCrossThreadCalls = false;
}
Remember that this is not proper way to do this but this will help you to solve your problem. Search for Thread safe calling.

Related

Terminate Window on another thread

I have a simple C# winform app where I spawn a new thread to show another winform. After a process is completed i want to close that form using the below code. The issue I have is that when I call busyForm.BeginInvoke it is bypassing the null check and throw and error. How to correctly close the winform in another thread?
static Indicator busyForm;
public static async Task Execute()
{
Thread busyIndicatorthread = new Thread(new ThreadStart(()=>FormThread()));
busyIndicatorthread.SetApartmentState(ApartmentState.STA);
busyIndicatorthread.Start();
}
private static void FormThread()
{
busyForm = new Indicator();
busyForm.Closed += (sender2, e2) => busyForm.Dispatcher.InvokeShutdown();
Dispatcher.Run();
}
public static Task Execute(){
Thread busyIndicatorthread = new Thread(new ThreadStart(()=>FormThread(hwind)));
busyIndicatorthread.SetApartmentState(ApartmentState.STA);
busyIndicatorthread.Start();
// dos some stuff
if (busyForm != null)
{
busyForm.BeginInvoke(new System.Action(() => busyForm.Close())); <--- throw null error
busyForm = null;
}
}
That is because before calling .Close() method, time has passed and it is not assured that busyForm exists anymore.
In fact, it is possible that, while the new System.Action(() => busyForm.Close() thread is starting, you main thread goes to busyForm = null;.
You can try moving the null to secondary thread.
if (busyForm != null)
{
busyForm.BeginInvoke(new System.Action(() =>
{
lock(busyForm){
busyForm.Close();
busyForm = null;
}
}));
}
Almost no application starts another message pump to display notifications. It's not needed. In all applications, the busy and progress dialog boxes are generated and displayed.by the UI thread. Operations that could block are performed in the background, eg in a background thread or far better, using async/await and Task.Run. The UI is updated using events or callbacks, eg using the Progress< T> class.
In this case though, it seems all that's needed is to display a form before a long-running task and hide it afterward:
public async void btnDoStuff_Async(object sender, EventArgs args)
{
//Disable controls, display indicator, etc
btnDoStuff.Enabled=false;
using var busyForm = new Indicator();
busyForm.Show();
try
{
var result=await Task.Run(()=> ActuallyDoStuffAndReturnResult());
//Back in the UI form
//Do something with the result
}
finally
{
//Close the busy indicator, re-enable buttons etc.
busyForm.Close();
btnDoStuff.Enabled=true;
}
}
The finally block ensures the UI is enabled and the busy form hidden even in case of error.
20+ years ago some Visual Basic 6 applications did start another Window message pump to act as a "server". Visual Basic 6 threading was very quirky, so people used various tricks to get around its limitations.
When you write this code:
busyForm.BeginInvoke(new System.Action(() => busyForm.Close())); <--- throw null error
busyForm = null;
The order in which it executes is almost certainly this:
busyForm = null;
busyForm.Close();
No wonder you're getting a null reference exception!
Simply set the form to null in your invoke. That'll fix it.
However, the correct way to do this is as Panagiotis Kanavos suggests.

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));

wpf what happens if ShowDialog() is called from a non-UI thread?

So I currently have this code below, which has a background worker call showdialog(). However, I thought that the UI cannot be updated on a background thread, so how does the dialog display? Does the dialog actually get opened on the UI thread? what happens?
public partial class ProgressDialog : Window
{
BackgroundWorker _worker;
public BackgroundWorker Worker
{
get { return _worker; }
}
public void RunWorkerThread(object argument, Func<object> workHandler)
{
//store reference to callback handler and launch worker thread
workerCallback = workHandler;
_worker.RunWorkerAsync(argument);
//display modal dialog (blocks caller)
//never returns null, but is a nullable boolean to match the dialogresult property
ShowDialog();
}
I have gotten suggestions that I just run the code and check, but how do i check whether the show dialog window was opened on a new thread or on the background thread itself? Not sure how I would check that.
Anyway this was just a post to try to help my understanding of what is actually happening in my code.
Anyway finally understood more of the comments, so I think I understand everything that is going on. Most of my real problems weren't caused by this dialog anyway, they were caused by updating observable collections from a non-ui thread while controls were bound to them.
Technically you are not changing a property on your Main thread just creating a instance of another object.
But it could help if you elaborate a bit more on your method ShowDialog().
I had also problem with calling ShowDialog() from non-UI thread. And my answer is that it depends on the thread which calls the ShowDialog(). If you set the ApartamentState property for this thread before its start then everything will work as called from the UI thread. I have finally ended up with such a code:
private async void button1_Click(object sender, EventArgs e)
{
var foo = new Foo();
// MessageBox.Show(foo.DirPath) - this works as a charm but
// if, it is called from non UI thread needs special handling as below.
await Task.Run(() => MessageBox.Show(foo.DirPath));
}
public class Foo
{
private string dirPath;
public string DirPath
{
get
{
if (dirPath == null)
{
var t = new Thread(() =>
{
using (var dirDialog = new FolderBrowserDialog())
{
if (dirDialog.ShowDialog() == DialogResult.OK)
dirPath = dirDialog.SelectedPath;
}
}
);
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
}
return dirPath;
}
set
{
dirPath = value;
}
}
}
I dont know for sure but i thought that the showDialog doesnt create the object only showing it. So when u say ShowDialog it only tells to show. So it will run on the UI thread instead of the backgroundworker
(dont know for sure)

STA threading exception in a Wpf application

I have a wpf application like this :
public CreateProject()
{
InitializeComponent();
_3DCAO.Temporary3DCAO.Close = false;
Userinitial fen = new Userinitial();
container.Content = fen;
Thread windowThread2 = new Thread(delegate() { verifing2(); });
windowThread2.IsBackground = true;
windowThread2.Start();
}
public void verifing2()
{
bool condition_accomplished = false;
while (!condition_accomplished)
{
if (Temporary3DCAO.Etape == 1)
{
_3DCAO.Settings set = new Settings();
if (container.Dispatcher.CheckAccess())
{
container.Content = set;
}
else
{
container.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>
{
container.Content = set;
}));
}
condition_accomplished = true;
}
}
}
In the method Verifing i'd like to instanciate a User Control
_3DCAO.Settings set = new Settings();
But this error appears :
The calling thread must be STA, as required by many components of the user interface
Why this exception appears?
How can i fix it?
WPF (in fact all Windows GUI interactions) must have any GUI interaction on a single GUI thread, because the GDI (the subsystem which deals with the GUI in Windows) is single threaded. Everything must be on that thread. That thread is also an STA thread.
You're changing container, setting it's Content, and you're doing it on the wrong thread. There are ways to get it to the right thread.
In the constructor or after calling InitializeComponents(), add this
this.guiContext = SynchronizationContext.Current;
..where guiContext is of type System.Threading.SynchronizationContext. You can then despatch work onto the GUI thread:
guiContext.Send(this.OnGuiThread, temp);
Where OnGuiThread is a method taking as object parameter and temp is the object sent to it.
This will mean re-organising your code, as not only do you have to create GUI objects (like "set" in your code) on the thread, you can only change them on that thread.
Cheers -

How to construct a new WPF form from a different thread in c#

I'm having trouble with the concept of threads and how to use them.
I'm trying to code a fairly basic chat program (as part of a larger program) and it currently works like this:
The 'NetworkSession' class receives the input from the server on a separate thread in a loop. If it receives input that indicates it should open a new chat window it constructs a new WPF class (ChatWindow) and displays it.
Originally I got the error that "The calling thread must be STA, because many UI components require this.". So i set the thread to be STA but now of course the WPF form is unusable because its running on the same thread as the blocking loop.
So my question is how do I create a new instance of a WPF form from within another thread.
I've seen alot of discussion about this but it tends to deal with running a delegate from a form that has already been constructed.
Here is some code.
while (Connected) //this loop is running on its own thread
{
Resp = srReceiver.ReadLine();
if (Resp.StartsWith("PING")) SendToServer("PONG");
if (Resp.StartsWith("CHAT FROM"))
{
String[] split = Resp.Split(' ');
Console.WriteLine("Incoming Chat from {0}", split[2]);
bool found = false;
if (Chats.Count != 0)
{
foreach (ChatWindow cw in Chats)
{
if (cw.User == split[2])
{
found = true;
cw.AddLine(cw.User, split[3]); // a function that adds a line to the current chat
}
}
}
if (!found)
{
ChatWindow temp = new ChatWindow(split[2], split[3]);
Chats.Add(temp); //this is a collection with T = ChatWindow
temp.Show();
}
}
}
If you're constructing NetworkSession from your UI Thread, you can snag a reference to the current Dispatcher that can manipulate the UI later.
NetworkSession.cs
private Dispatcher _dispatcher;
public NetworkSession()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
//any thread can call this method
public void DoStuff()
{
Action action = () =>
{
ChatWindow temp = new ChatWindow(split[2], split[3]);
Chats.Add(temp);
temp.Show();
};
_dispatcher.BeginInvoke(action);
}
The code below, which I took from here worked for me:
public static void StartChatWindow()
{
Thread thread = new Thread(() =>
{
ChatWindow chatWindow = new ChatWindow();
chatWindow.Chat(); // Do your stuff here, may pass some parameters
chatWindow.Closed += (sender2, e2) =>
// Close the message pump when the window closed
chatWindow.Dispatcher.InvokeShutdown();
// Run the message pump
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
What you really need to do is construct the window/form on your main UI thread. You probably need to define a delegate that you can call from your network thread and that delegate should have a method attached that will call this.Dispatcher.BeginInvoke() -> inside which you will construct your window.
The this.Dispatcher.BeginInvoke() call is necessary to execute code on the UI thread, otherwise even with a delegate, code would be executed on the network thread.
Both the delegate and the method for creating a new chat window should probably be attached to the MainWindow...

Categories