Using MessageBox to show exception information in multithreaded application - c#

Hi I'm working with winform and trying to use MessageBox for exception handling.
The weird thing here is, the MessageBox appears only after the main form ("Form1" in the code below) is closed.
public class Worker {
/* edited (see below)
public void doWork() {
try {
// do something
client.Connect(serverAddress);
stream = client.GetStream();
}
catch(Exception e) {
MessageBox.Show(e.ToString(),
"This will not show up until Form1 is closed");
}
}
*/
}
public class Form1 {
/* edited (see below)
* public void threadProc() {
* Worker worker = new Worker();
* worker.doWork();
* }
*/
void button_Click(object sender, EventArgs e) {
// create a thread that will end up throwing an exception
Thread thread = new Thread(threadProc);
thread.Start();
}
}
What could be a better way to use MessageBox for exception handling?
...So I added some codes for MessageBox-ing in the UI thread, but the problem remains.
public class WorkExceptionArgs : EventArgs {
public Exception e;
public WorkExceptionArgs (Exception e) { this.e = e; }
}
public partial class Worker1 { // renamed (Worker->Worker1)
/* (edited) Now Worker1 doesn't trigger any event (see below)
public event EventHandler<WorkExceptionArgs> workException;
*/
public void doWork() {
try {
// do something
client.Connect(serverAddress);
stream = client.GetStream();
}
catch(Exception e) {
/* (edited) suppose Worker1 never throws any exception (see below)
* // trigger event that will cause MessageBox-ing by UI thread
* workException(this, new WorkExceptionArgs(e));
*/
}
}
}
public partial class Form1 {
public void threadProc() {
Worker1 worker1 = new Worker();
/* (edited) Now Worker1 never throws any exception
* worker.workException += new EventHandler<WorkException>(worker_WorkException);
*/
worker1.doWork();
// (added) After doWork() is done, Form1 creates Worker2
Worker2 w2 = new Worker2(this, this.form2);
w2.workException += new EventHandlerArgs<WorkExceptionArgs>(form2.worker2_WorkException);
w2.doSomeOtherWork();
}
/* public void worker_WorkException(object sender, WorkExceptionArgs eArg) {
* MessageBox.Show(eArg.e.ToString(), "Still not showing");
* } */
Form2 form2 = new Form2(); // (added) At first form2 is hidden (see below)
}
Actually there have been another form and another worker. Once Worker(Worker1) made connection to the server, Form1 hides (.Hide()), Form2 shows (.Show()), and Worker2 starts working with the connection Worker1 made.
public class Worker2 {
Worker2(Worker1 w1, Form2 frm2) { this.w1=w1; this.frm2=frm2; }
public Worker1 w1;
public Form2 frm2;
public event EventHandler<WorkExceptionArgs> workException;
public void doSomeOtherWork() { // do some other, using data in Worker 1.
try { // This will throw an exception
BinaryFormatter formatter = new BinaryFormatter();
MyObj mo = (MyObj)formatter.Deserialize(w1.getStream());
}
catch(Exception e) {
workException(this, new WorkExceptionArgs(e));
}
}
}
public class Form2 {
public Form2(Form1 frm1) { // to switch from frm1 to frm2
InitializeComponent();
this.frm1 = frm1;
}
public Frm1 frm1 {get;set;}
public void worker2_WorkException(object sender, WorkExceptionArgs ea) {
MessageBox.Show(this, ea.e.ToString(), "SHOWS ONLY IF FORM2 IS CLOSED");
}
}
public partial class Form1 {
delegate void switchWindow_Callback();
public void switchWindow() { this.Hide(); form2.Show(); }
public void switchWindowCb(object sender, EventArgs e) {
if(this.InvokeRequired) {
SwitchWindow_Callback hcb = new SwitchWindow_Callback(switchWindow);
this.Invoke(hcb, new object[] {});
}
else { this.switchWindow(); }
}
}

Actually I'll bet the MessageBox is appearing behind the main form, and you just don't see it until you close it.
You'd be much better off letting the UI thread (the one that created and owns Form1) do the MessageBox-ing. You either want to make events, or otherwise have an error callback delegate in your worker class.
However, BackgroundWorker may be worth checking out here rather than trying to roll your own. Assuming it's a fatal exception, you can save and retrieve the error state, and you get an event automatically called when the thread finishes.

You should really be locking down the doWork method so that multiple threads cant access it at the same time, they need to queue.
Look into "Joining Threads". Imagine if you get two exception at the same time. Your app will fall over. Locking down the area of code that will be duplicated repeatedly will form a queue for your threads to access the area of code that handles the exception.

As lc stated, it's quite likely that your message box is appearing behind the main form, and therefore you only see it when the main form is closed.
The model I use to deal with unhandled exceptions in a Windows Forms app looks like this:
// Switch-off the Windows Forms default handler for unhandled exceptions.
// NB From .NET 4 upwards, this won't work if the process state is corrupted.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// Setup event handler to intercept an unhandled exception on a UI thread .
// NB The exception will still terminate the application.
// But you can show a MessageBox in the event handler and log the exception.
Application.ThreadException +=
new ThreadExceptionEventHandler(App_UiThreadException);
// Setup event handler to intercept an unhandled exception on a non-UI thread.
AppDomain.CurrentDomain.UnhandledException += new
UnhandledExceptionEventHandler(App_NonUiThreadException);
// Run the application (open main form etc).
The first line says that you want to catch any unhandled exception and deal with it yourself rather than letting the WinForms infrastructure deal with it. Note that from .NET 4 onwards, this setting won't work for an exception that corrupts process state (for example OutOfMemory).
If you have an unhandled exception on a UI thread, the second line will trigger a procedure you create called *App_UiThreadException*. That's where your MesssageBox code should go.
If you have an unhandled exception on a non-UI thread, the last line will trigger a procedure you create called *App_NonUiThreadException*. That's where your MesssageBox code should go.

Related

How can I instantiate windows form objects for multiple clients in C #

I tried to instantiate an object of the WebBrowser class to make a query on a page and return a result but an error is generated:
An instance of the ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be created because the Current thread is not in a unprocessed container.
I have tried to implement this in different ways but I do not achieve the desired result.
Here is my code
// At beginig of class Form
public delegate void DataRecieved(ConexionTcp conexionTcp, string data);
public event DataRecieved OnDataRecieved;
private void Form1_Load(object sender, EventArgs e)
{
OnDataRecieved += MensajeRecibido;
}
private void MensajeRecibido(ConexionTcp conexionTcp, string datos)
{
WebBrowser myweb= new WebBrowser();
myweb.Navigate("http://localhost/Php/Final3");
myweb.Document.GetElementById("user").InnerText = "user";
myweb.Document.GetElementById("pass").InnerText = "pass";
myweb.Document.GetElementById("Encode").InvokeMember("Click");
if ("resultado" == myweb.Document.GetElementById("pass_sha_hash").InnerText)
{
textbox1.Text="Completado";
}
}
Can anyone find out what am i doing wrong?
Looks like you are trying to access a Windows Form Control from a thread.
Accessing Windows Form Controls from another thread is not safe. So you are getting this error.
Please refer this document for more details: https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls
You can use Background Threads to do your operation. Otherwise marking the thread's apartment state to STA might help.
In the below example, I have used STA thread, otherwise, I will get a similar error.
public partial class Form1 : Form
{
Thread t;
public delegate void DataRecieved();
public event DataRecieved OnDataRecieved;
public Form1()
{
InitializeComponent();
t = new Thread(new ThreadStart(this.TriggerEvent));
// Make sure that you are using STA
// Otherwise you will get an error when creating WebBrowser
// control in the Navigate method
t.SetApartmentState(ApartmentState.STA);
t.Start();
OnDataRecieved += Navigate;
}
public void Navigate()
{
WebBrowser b = new WebBrowser();
b.Navigate("www.google.com");
}
public void TriggerEvent()
{
Thread.Sleep(10000);
OnDataRecieved();
}
}
Below is the error:
System.Threading.ThreadStateException: 'ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.'
Hope this helps.

Closing parent form from child thread

I've been scratching my head on this one for a while now and despite looking for solutions, im not quite understanding the implementations (several answers on stack overflow have already been looked at)
My program loads a splash page when it is opened, during which it checks for a database connection. If there is a connection, the splash page closes and the main form loads, otherwise it provides an error message then closes completely.
public partial class StartupSplash : Form
{
ThreadStart th;
Thread thread;
public StartupSplash()
{
InitializeComponent();
th = new ThreadStart(DbAvaliable);
thread = new Thread(th);
thread.Start();
}
private void DbAvaliable()
{
Boolean result = false;
using (var connectiontest = new SqlConnection("myConnString"))
try
{
connectiontest.Open();
result = true;
}
catch (Exception ex)
{
result = false;
}
if (result)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainWindow());
}
else
{
MessageBox.Show("Unable to establish database connection. Please check your data connection and try again.");
}
}
}
I understand that I can't simply call this.Close() due to cross thread issues. I've read something about invoking methods, but im not too clear how to achieve the result above.
Initially I tried to use form load/shown events instead of alternate threads, but the image on the forms failed to load until after the messagebox had shown the error (rather than displaying, then running the connection check)
Could you set up an event to fire on Form2 with the results of the db check? Subscribe to the event on Form1 and tell it to close if the conditions warrant.
Not sure if it would work or not, but something like:
public Form2 : Form
{
public delegate void DbCheckHandler(object sender, DbEventArgs e);
public event DbCheckHandler DbCheckComplete;
//check the db
DbCheckComplete(this, new DbEventArgs { ShouldClose = true; });
}
public Form1 : Form
{
Form2 form2 = new Form2();
form2.DbCheckComplete += new DbCheckHandler(CheckDbResult);
form2.Show();
private void CheckDbResult(object sender, DbEventArgs e)
{
if(e.ShouldClose)
{
this.Close();
}
}
}
With some help from previous answers posted by Hans Passant (here and here), My solution was as follows:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
new MyApp().Run(args);
}
class MyApp : WindowsFormsApplicationBase
{
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new StartupSplash();
}
protected override void OnCreateMainForm()
{
Boolean result = false;
using (var connectiontest = new SqlConnection("myConnectionString"))
try
{
connectiontest.Open();
result = true;
}
catch (Exception ex)
{
result = false;
}
// pause not needed while checking for db connection as that takes its own amount of time to complete.
if (result)
{
System.Threading.Thread.Sleep(3000); //pause moved here to give the splash some time on screen if db connection available
this.MainForm = new MainWindow();
}
else
{
MessageBox.Show("Unable to connect to the database");
Environment.Exit(1); // shuts down the program if database connection not avaliable.
}
}
}

Setting Control properties from separate thread/class

I've searched and can't find a solution that helps me get text from a thread running in a separate class, back to a listbox on the form that created the thread.
Basically I have a class that holds a "test", it is called in it's own thread from a test window. What I want to be able to do is add text to a listbox on the main form to let the user know what is going on with a test. All the examples I can find on Invoke show how to do it within the same class.
Where I start the thread:
PermeabilityTest Run_Test = new PermeabilityTest();
public Thread WorkerThread;
private void button2_Click(object sender, EventArgs e)
{
//enable timer for test duration display
timer1.Enabled = true;
//create and start new thread.
WorkerThread = new Thread(Run_Test.RunTest);
WorkerThread.Start();
}
Here is my class that actually does the work, where I need to get text back to a listbox on a separate form from.
public class PermeabilityTest
{
//volatile alerts the compiler that it will be used across threads.
private volatile bool aborted;
public void RequestStop()
{
//handle saving data file here as well.
aborted = true;
}
public void RunTest()
{
//reference the comms class so we can communicate with the machine
PMI_Software.COMMS COM = new COMMS();
//some test stuffs here
int x = 0;
while( x < 100 && !aborted)
{
System.Diagnostics.Debug.Write("Well here it is, running it's own thread." + Environment.NewLine);
COM.Pause(1);
}
}
}
I would appreciate any one who could help me understand how to get some text back to a listbox on the same form that has the button which starts the thread.
Option 1: (Preffered) Add an event on PermeabilityTest and register on that event in your main form.
Then modify the content of your List box from within your main form.
Example:
Your main form:
PermeabilityTest Run_Test = new PermeabilityTest();
public Thread WorkerThread;
public form1()
{
// Register on the Progress event
Run_Test.Progress += Run_Test_Progress;
}
void Run_Test_Progress(string message)
{
if(listBox.InvokeRequired)
{
// Running on a different thread than the one created the control
Delegate d = new ProgressEventHandler(Run_Test_Progress);
listBox.Invoke(d, message);
}
else
{
// Running on the same thread which created the control
listBox.Items.Add(message);
}
}
private void button2_Click(object sender, EventArgs e)
{
//enable timer for test duration display
timer1.Enabled = true;
//create and start new thread.
WorkerThread = new Thread(Run_Test.RunTest);
WorkerThread.Start();
}
new Delegate:
public delegate void ProgressEventHandler(string message);
Modified PermeabilityTest class:
public class PermeabilityTest
{
//volatile alerts the compiler that it will be used across threads.
private volatile bool aborted;
public event ProgressEventHandler Progress;
public void RequestStop()
{
//handle saving data file here as well.
aborted = true;
}
public void RunTest()
{
//reference the comms class so we can communicate with the machine
PMI_Software.COMMS COM = new COMMS();
//some test stuffs here
int x = 0;
while (x < 100 && !aborted)
{
// Report on progress
if(Progress != null)
{
Progress("This message will appear in ListBox");
}
System.Diagnostics.Debug.Write("Well here it is, running it's own thread." + Environment.NewLine);
COM.Pause(1);
}
}
}
Option 2:
You could make PermeabilityTest an inner class of your main form, and by doing so, allow it to access private members of your main form.
Then you need to pass a reference of your main form to the constructor of PermeabilityTest and keep it as a member.
Option 3:
pass your list box to the constructor of PermeabilityTest
Don't forget to use Invoke on your control since you are running from a different thread.

Cross-thread operation not valid: Asynchronous delegates error

I've been trying to learn delegates.I just created a button,label and checkbox. If I click checkbox, the time format changes. If i click the button , i print the date accordingly. However when trying to use asynchromous delegate i.e., to use another thread, i am stuck with an error
public delegate void AsyncDelegate(bool seconds);
public partial class Form1 : Form
{
AsyncDelegate ad;
TimeZ t = new TimeZ();
public Form1()
{
InitializeComponent();
}
private void btn_async_Click(object sender, EventArgs e)
{
ad = new AsyncDelegate(t.GetTime);
AsyncCallback acb = new AsyncCallback(CB);
if (chk_sec.Checked)
{
ad.BeginInvoke(true, acb, null);
}
else
ad.BeginInvoke(false, acb, null);
}
public void CB(IAsyncResult ar)
{
t.Tim = ar.ToString();
ad.EndInvoke(ar);
lbl_time.Text = t.Tim;
}
and in another class library i get Timez used above. I add a reference of it in the project
public class TimeZ
{
private string tim;
public string Tim
{
get
{
return tim;
}
set
{
tim = value;
}
}
public string GetTime(bool seconds)
{
if (seconds)
{
return DateTime.Now.ToLongTimeString();
}
else
return DateTime.Now.ToShortTimeString();
}
}
However i get this error when i run the program:
Cross-thread operation not valid: Control 'lbl_time' accessed from a thread other than
the thread it was created on.
Can u help me out on how to solve this?
You cannot access forms and controls properties and methods from a thread that is not the form thread.
In windows, each window is bound to the thread that created it.
You can do that only with Control.BeginInvoke or the more useful System.Threading.SynchronizationContext class.
See http://msdn.microsoft.com/it-it/library/system.threading.synchronizationcontext(v=vs.95).aspx
See http://msdn.microsoft.com/it-it/library/0b1bf3y3(v=vs.80).aspx
It means, you have to post through synchronization context for example another async delegate in form thread.
public partial class Form1 : Form
{
AsyncDelegate ad;
TimeZ t = new TimeZ();
// Our synchronization context
SynchronizationContext syncContext;
public Form1()
{
InitializeComponent();
// Initialize the synchronization context field
syncContext = SynchronizationContext.Current;
}
private void btn_async_Click(object sender, EventArgs e)
{
ad = new AsyncDelegate(t.GetTime);
AsyncCallback acb = new AsyncCallback(CB);
if (chk_sec.Checked)
{
ad.BeginInvoke(true, acb, null);
}
else
{
ad.BeginInvoke(false, acb, null);
}
}
public void CB(IAsyncResult ar)
{
// this will be executed in another thread
t.Tim = ar.ToString(); // ar.ToString()???? this will not give you the time for sure! why?
ad.EndInvoke(ar);
syncContext.Post(delegate(object state)
{
// This will be executed again in form thread
lbl_time.Text = t.Tim;
}, null);
}
I don't know why you need an asynchronous callback to print time however :) really don't know why, thinking it is just some test code.

Thread.Start not working

I am using the functions of the following class to invoke a messagebox. I am using thread.Start method to show the messagebox. The problem is it is not reaching to the respective function when thread.Start is called. Am i missing anything?
class MessageManager
{
string _message;
public MessageManager(string message)
{
_message = message;
}
public void ShowBigMessage()
{
Thread thread = new Thread(DisplayBigMessage);
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start();
// thread.Join();
}
public void ShowNormalMessage()
{
Thread thread = new Thread(DisplayNormalMessage);
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start();
//thread.Join();
}
private void DisplayBigMessage()
{
BigAppMessage appMessage = new BigAppMessage(_message);
appMessage.Show();
}
private void DisplayNormalMessage()
{
AppMessage appMessage = new AppMessage(_message);
appMessage.ShowDialog();
}
}
This is called inside a thread/delegate as below. I added this code into my program becuase it was raising
The calling thread must be STA, because many UI components require
this.
exception before
MessageManager message = new MessageManager("This is a test message.");
message.ShowBigMessage();
public partial class BigAppMessage : Window
{
public BigAppMessage(String message)
{
InitializeComponent();
myControl.setMessage(message); // mycontrol is just user control with a
//label on it
}
}
The Show() method requires a message loop. Fix:
private void DisplayBigMessage()
{
Application.Run(new BigAppMessage(_message));
}
There's already a message loop built into the ShowDialog() method. Using a thread to just display a window has no advantages, only problems.
In visual studio go to Debug->Exceptions and check the "thrown" box next to CLR exceptions. this will tell you where your problem is. Probably its a cross thread issue since you would ordinarily only interact with the UI on the UI thread.

Categories

Resources