I have a C# windows application in which I create an instance of a some class, in which it has a member which is a window form and this form has a button that when I click it I open a new form that can run a worker thread, let's say:
public static void Main()
{
MyClass mc = new MyClass();
mc.ShowForm();
}
in MyClass.cs:
public void ShowForm()
{
MyFirstForm firstForm = new MyFirstForm();
firstForm.Show();
}
in MyFirstForm.cs:
private void myButton_Click(object sender, EventArgs e)
{
MySecondForm secondForm = new MySecondForm();
secondForm.Show();
}
in MySecondForm.cs:
private void startButton_Click(object sender, EventArgs e)
{
var worker = new Thread(StartWork);
worker.Start();
}
private void stopButton_Click(object sender, EventArgs e)
{
m_stopped = true;
}
private void StartWork()
{
while(!m_stopped)
{
//work...
}
}
When I run my app, clicks myButton, then click startButton, and then exit my app (I have a tray Icon that when clicked to exit, call base.Shutdown() ), the second form stays hanging and non responsive.
My original question was, what is the best way to notify the second form that it should close and stop the running thread, but during writing this post I noticed that I can use Task.Factory.StartNew(StartWork); and when I tried it, it worked without a hitch.
So now I have another question which is why is this happening?
I tried registering to Closing , Closed , FormClosing events and setting m_stopped = true but they were not getting called.
EDIT:
As #drf suggested the thread is a foreground thread hence the app is blocked while it runs, so adding:
worker.IsBackground = true;
fixed it.
Your thread is currently a foreground thread which will prevent the process from exiting until the thread finishes. http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground(v=vs.110).aspx
Change your thread startup code to this:
private void startButton_Click(object sender, EventArgs e)
{
var worker = new Thread(StartWork);
worker.IsBackground = true;
worker.Start();
}
I think for closing application you should use Application.Exit Method:
It informs all message pumps that they must terminate, and then closes all application windows after the messages have been processed.
Also you could track FormClosing event of each form. Check if Thread.IsAlive then Thread.Abort() or cancel closing.
Related
I believe I have a misunderstanding about either how a lock works or how the System.Windows.Forms.Timer works in C#.
So I made a simple Windows Forms Application (.NET Framework) and I added a Timer and a Button to the Form from the Toolbox. The Button starts the Timer when clicked, and the Timer enters a lock on a dummy object and blocks it on the Tick event. For the Button's Click event I have the following method:
private void button1_Click(object sender, EventArgs e)
{
timer1.Enabled = true;
}
And for the Timer's Tick event I have this method:
readonly object lockObj = new object();
private void timer1_Tick(object sender, EventArgs e)
{
lock (lockObj)
{
MessageBox.Show("Entered the lock!");
MessageBox.Show("Exiting the lock...");
}
}
Everything else is left to default and there is no additional code.
I expected this program to show a single MessageBox with the text "Entered the lock!", then after I close it and also the following one with the message "Exiting the lock..." I thought the lock would be released and a queued up Tick event if any would acquire the lock, the process reapeating. Instead, the "Entered the lock!" MessageBox keeps opening multiple times without having to close it, as if every Tick event call enters the lock even if nobody releases it.
I tried to replicate this in a Console Application but with no luck. I'd appreciate a hint about what causes this problem so I know where to look into it.
Alternative code you can test in a Windows Forms Application:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Lock_Test_2
{
public partial class Form1 : Form
{
Timer timer1;
readonly object lockObj = new object();
public Form1()
{
InitializeComponent();
Button button1 = new Button();
button1.Location = new Point(100, 100);
button1.Size = new Size(187, 67);
button1.Text = "button1";
button1.Click += button1_Click;
Controls.Add(button1);
timer1 = new Timer();
timer1.Tick += timer1_Tick;
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
lock (lockObj)
{
MessageBox.Show("Entered the lock!");
MessageBox.Show("Exiting the lock...");
}
}
}
}
System.Windows.Forms.Timer dispatches its events via a windows message loop.
MessageBox.Show shows a messagebox and then pumps the windows message loop as a nested loop. This can include dispatching more events for a timer.
Since only a single thread (the UI thread) is involved, and lock is reentrant, that's why you get multiple message boxes shown.
In a C# desktop application, a backgroundworker responsible for saving application state is being called in 2 situations. Once while the application is running. That works fine. Other when application is being closed, backgroundworker is called to save the application state but before it starts saving, the application is closed and nothing gets saved.
I tried to solve it by using the AutoReset event class in DoWork and RunWorkerCompleted but didnt work because application closed before backgroundworker could save any thing.
Question is - how can I make the main thread wait until backgroundworker finishes saving?
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
this.saveAHToolStripMenuItem_Click(this, e);
}
private void saveAHAsToolStripMenuItem_Click(object sender, EventArgs e)
{
this.backgroundWorkerMain1.RunWorkerAsync(args);
}
private void backgroundWorkerMain1_DoWork(object sender, DoWorkEventArgs e)
{
saveMethod();
}
private void backgroundWorkerMain1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
showResultOfSaving();
}
Is it WinForms?
Maybe you could register to the OnClosing event.
Within it, set a private property IsClosing to true.
Mark the eventhandler e as e.Handled = true.
Register to the BackgroundWorker event RunWorkerCompleted. Within it, check if the IsClosing property is set and in that case MainForm.Close() the application.
Edit:
using System;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;
namespace BgWorker
{
public partial class Form1 : Form
{
BackgroundWorker _bgWorker;
bool _iNeedToCloseAfterBgWorker;
public Form1()
{
InitializeComponent();
}
void Form1_Load(object sender, EventArgs e)
{
_bgWorker = new BackgroundWorker();
_bgWorker.DoWork += _bgWorker_DoWork;
_bgWorker.RunWorkerCompleted += _bgWorker_RunWorkerCompleted;
}
void _bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Done!");
if (_iNeedToCloseAfterBgWorker)
Close();
}
void _bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Do long lasting work
Thread.Sleep(2000);
}
void btnWorkIt_Click(object sender, EventArgs e)
{
// Note how the Form remains accessible
_bgWorker.RunWorkerAsync();
}
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (_iNeedToCloseAfterBgWorker || _bgWorker.IsBusy) return;
e.Cancel = true;
_iNeedToCloseAfterBgWorker = true;
_bgWorker.RunWorkerAsync();
}
}
}
It would be cleaner to place the SaveState code in a separate method, which is called from the background worker and (synchronously) when you close the application.
Either that, or you can block the application from closing based on a manual reset event (ManualResetEvent-class).
Just throwing an idea out there.
Perhaps a volatile DateTime property on the MainForm.
(Note - DateTimes cannot be volatile it appears, so you could use the string representation instead)
public volatile string _lastUpdated;
This property gets updated everytime the save event is carried out on the background worker thread.
OnClose will check the Time Difference between DateTime.Now and the stored date time.
Eg:
var timeDiff = DateTime.Now - _lastSavedTime;
If the OnClose detects that the timeDiff.TotalSeconds is < 30 (less than 30 seconds)
You can then trigger the save event manually from the main thread, prior to the close event being completed.
This however won't protect against Process.Kill - very little can protect against that.
All I can suggest is that you manage your saves in a smart way.
Eg: Save to a new File each time, keeping the last 5 saves.
When the 6th save is made, delete the oldest save etc
This is to account for the potential corruption that may happen from a Process.Kill scenario. Means you will still have at least a 60 second backup, in case the 30 second backup fails.
My solution to this problem is to wait untill backgroundworker is fnished by adding following after backgroundworker async call.
while (this.backgroundWorkerMain1.IsBusy)
{
Application.DoEvents();
}
I have an application that when busy will open a busy form (FormWaitingForm) to indicate to the user that the application is busy. How do i close FormWaitingForm in the event backgroundWorker1_RunWorkerCompletedbelow ?
private void radButtonCheckFiles_Click(object sender, EventArgs e)
{
var bw = new BackgroundWorker();
// define the event handlers
bw.DoWork += new DoWorkEventHandler(ProcessTickTemp);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
bw.RunWorkerAsync(); // starts the background worker
// execution continues here in parallel to the background worker
using (var FormWaitingForm = new WaitingForm()) //
{
var result = FormWaitingForm.ShowDialog();
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// How do i close FormWaitingForm here ?
}
You could try something like this. Retain a reference to the form outside of the click method and then open it non-modally (so that you don't have to wait for the user to close it).
WaitingForm formWaitingForm;
private void radButtonCheckFiles_Click(object sender, EventArgs e)
{
// background code here
formWaitingForm = new WaitingForm();
formWaitingForm.Show();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
formWaitingForm.Close();
formWaitingForm.Dispose();
}
You would have to add some code to handle if the user closes the waiting form without waiting for you to do it.
That said, the way I usually implement a waiting/progress-type form is to incorporate the background process into the progress form itself and show something like a progress bar.
This link might give you some more ideas.
I've been trying to figure this out for the past day or so.
I have a program which has Form1, and a button that spawns Form2 in a new thread.
I also have another button on Form1 that should close Form2, but since Form2 is in another thread, I cannot touch that object directly.
I could do t.Abort() but that throws an exception.
How can I gracefully touch the other thread? Do stuff to it?
For example, how do I close the form from within Form1?
I've searched google for "how to close a form from within another thread" and found several links hinting at Invoke and Delegate, but after trying some things, I obviously can't figure out how to use it properly.
Can anyone help me to understand how it would apply to the code I have, so that I can understand how they can be used? in which context, etc?
I've uploaded the project to github for your convenience: https://github.com/powercat/WindowsFormsApplication7/archive/master.zip
--
Code:
[Form1.cs]
public void FormThread()
{
Application.Run(new Form2());
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(FormThread));
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
//Need to close Form2 from here.
}
[Form2.cs]
has the other form code.
In general, having two threads for two forms is not a good idea. It's almost always a better idea to have all of your forms on the main, UI thread, and move your logic and work onto background threads instead.
That being said, if the Form is being run in the separate thread, you should be able to use BeginInvoke to close it:
otherForm.BeginInvoke(new Action(() => otherForm.Close()));
Edit:
In your case, you'd need to save the instance:
Form2 otherForm;
public void FormThread()
{
otherForm = new Form2();
Application.Run(otherForm);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(FormThread));
t.SetApartmentState(ApartmentState.STA); // THIS IS REQUIRED!
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
//Need to close Form2 from here.
if (otherForm != null)
{
otherForm.BeginInvoke(new Action( () => otherForm.Close() ));
otherForm = null;
}
}
i'm loading screen in backgroundworker:
private void LSLoadingScreen(object sender, DoWorkEventArgs e)
{
LoadingScreen ls = new LoadingScreen(this.timerStart);
ls.ShowDialog();
while (LoadingScreen.CancellationPending)
{
ls.Dispose();
LoadingScreen.Dispose();
}
but my loadingScreen doesn't dispose when i use this code in other function:
LoadingScreen.CancelAsync();
timerStart = false;
LoadingScreen.Dispose();
How to dispose it properly?
Firstly, ShowDialog() will prevent the rest of the code executing until the dialog is closed - which you are never doing.
Even when it does close, it will evaluate the while loop (which will most likely be false so skipped) and then your backgroundworker will be finished.
If all you are doing is showing a dialog then I would just do this on the main thread, and have your loading process on the background worker..
Fire background worker (which does loading code)
Show your loading dialog
On BackgroundWorkerCompleted event, close your loading dialog
Try to get all your UI elements in the main UI thread.
Hope that helps
EDIT:
Based on your comment...
public partial class MainForm:Form
{
LoadingScreen ls;
public MainForm()
{
}
public void StartLoad()
{
ls = new LoadingScreen(this.timerStart);
backgroundWorker.RunWorkerAsync();
ls.Show();
}
void backgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
//Loading code goes here
}
void BackgroundWorkerMainRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(ls != null)
ls.Close();
}
}