I'm getting some troubles with my Winforms C# app.
I wish to make form named Popup closing after some operations in main thread are done. The problem is an exception caused by cross-thread form closing.
private void loginButton_Click(object sender, EventArgs e)
{
LoginProcess.Start(); // Running Form.show() in new thread
ActiveAcc.IsValid = false;
ActiveAcc.Username = userBox.Text;
try
{
LoginCheck(userBox.Text, passBox.Text);
}
catch (IOException)
{
MessageBox.Show("..");
return;
}
catch (SocketException)
{
MessageBox.Show("..");
return;
}
if (ActiveAcc.IsValid)
{
MessageBox.Show("..");
Close();
}
else
{
Popup.Close(); // Error caused by closing form from different thread
MessageBox.Show("");
}
}
public Login() // 'Main' form constructor
{
InitializeComponent();
ActiveAcc = new Account();
Popup = new LoginWaiter();
LoginProcess = new Thread(Popup.Show); //Popup is an ordinary Form
}
I've been trying to use various tools such as LoginProcess.Abort() or Popup.Dispose() to make it work properly, but even if app is working on runtime environment its still unstable due to Exceptions which are thrown.
I would be grateful for any help, and I am sorry for ambiguities in issue describing.
Why don't you let the UI thread do UI stuff like opening and closing Forms, and spawn the other thread (or background worker, or async task) to do the other stuff?
IMO, having other threads attempt to interact with elements on the UI thread (e.g., have a background thread directly set the text of a label or some such) is asking for heartache.
If you simply must keep your code as is, here is a fairly simple thing you could do. In Popup, add a static bool that defaults to true. Also in Popup, add a timer task that once every X milliseconds checks the status of that boolean. If it finds that the value has been set to false, let Popup tell itself to close within that timer tick.
I'm not crazy about this design, but it could look something like:
public partial class Popup : Form
{
public static bool StayVisible { get; set; }
private System.Windows.Forms.Timer timer1;
public Popup()
{
StayVisible = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (!StayVisible) this.Close();
}
}
Then, from another thread, when you want Popup to close, call
Popup.StayVisible = false;
Better yet, you would fire an event that Popup would receive so that it could close itself. Since you intend to use multiple threads, you'll have to deal with raising events cross-thread.
Related
To simplify the explanation of the strange behavior I am experiencing, I have this simple class named Log which fires 1 log events every 1000msec.
public static class Log
{
public delegate void LogDel(string msg);
public static event LogDel logEvent;
public static void StartMessageGeneration ()
{
for (int i = 0; i < 1000; i++)
{
logEvent.Invoke(i.ToString());
Task.Delay(1000);
}
}
}
I have the Form class below which is subscribed to the log events of the Log class so it can handle them and display in a simple text box.
Once a log message arrives, it is added to a list. Every 500msec, a timer object access that list so its content can be displayed in a text box.
public partial class Form1 : Form
{
private SynchronizationContext context;
private System.Threading.Timer guiTimer = null;
private readonly object syncLock = new object();
private List<string> listOfMessages = new List<string>();
public Form1()
{
InitializeComponent();
context = SynchronizationContext.Current;
guiTimer = new System.Threading.Timer(TimerProcessor, this, 0, 500);
Log.logEvent += Log_logEvent;
}
private void Log_logEvent(string msg)
{
lock (syncLock)
listOfMessages.Add(msg);
}
private void TimerProcessor(object obj)
{
Form1 myForm = obj as Form1;
lock (myForm.syncLock)
{
if (myForm.listOfMessages.Count == 0)
return;
myForm.context.Send(new SendOrPostCallback(delegate
{
foreach (string item in myForm.listOfMessages)
myForm.textBox1.AppendText(item + "\n");
}), null);
listOfMessages.Clear();
}
}
private void button1_Click(object sender, EventArgs e)
{
Log.StartMessageGeneration();
}
}
The problem I see is that sometimes, there is a dead lock (application stuck). Seems that the 2 locks (1st one for adding to the list and the 2nd one for "retrieving" from the list) are somehow blocking each others.
Hints:
1) reducing the rate of sending the messages from 1 sec to 200msec seems to help (not sure why)
2) Somehow something happens when returning to the GUI thread (using the synchronization context) and accessing the GUI control. If I don't return to the GUI thread, the 2 locks are working fine together...
Thanks everyone!
There's a few problems with your code, and a few... silly things.
First, your Log.StartMessageGeneration doesn't actually produce a log message every second, because you're not awaiting the task returned by Task.Delay - you're basically just creating a thousand timers very quickly (and pointlessly). The log generation is limited only by the Invoke. Using Thread.Sleep is a blocking alternative to Task.Delay if you don't want to use Tasks, await etc. Of course, therein lies your biggest problem - StartMessageGeneration is not asynchronous with respect to the UI thread!
Second, there's little point in using System.Threading.Timer on your form. Instead, just use the windows forms timer - it's entirely on the UI thread so there's no need for marshalling your code back to the UI thread. Since your TimerProcessor doesn't do any CPU work and it only blocks for a very short time, it's the more straight-forward solution.
If you decide to keep using System.Threading.Timer anyway, there's no point in manually dealing with synchronization contexts - just use BeginInvoke on the form; the same way, there's no point in passing the form as an argument to the method, since the method isn't static. this is your form. You can actually see this is the case since you omitted myForm in listOfMessages.Clear() - the two instances are the same, myForm is superfluous.
A simple pause in the debugger will easily tell you where the program is hung - learn to use the debugger well, and it will save you a lot of time. But let's just look at this logically. StartMessageGeneration runs on the UI thread, while System.Threading.Timer uses a thread-pool thread. When the timer locks syncLock, StartMessageGeneration can't enter the same lock, of course - that's fine. But then you Send to the UI thread, and... the UI thread can't do anything, since it's blocked by StartMessageGeneration, which never gives the UI an opportunity to do anything. And StartMessageGeneration can't proceed, because it's waiting on the lock. The only case where this "works" is when StartMessageGeneration runs fast enough to complete before your timer fires (thus freeing the UI thread to do its work) - which is very much possible due to your incorrect use of Task.Delay.
Now let's look on your "hints" with all we know. 1) is simply your bias in measurements. Since you never wait on the Task.Delay in any way, changing the interval does absolutely nothing (with a tiny change in case the delay is zero). 2) of course - that's where your deadlock is. Two pieces of code that depend on a shared resource, while they both require to take posession of another resource. It's a very typical case of a deadlock. Thread 1 is waiting for A to release B, and thread 2 is waiting for B to release A (in this case, A being syncLock and B being the UI thread). When you remove the Send (or replace it with Post), thread 1 no longer has to wait on B, and the deadlock disappears.
There's other things that make writing code like this simpler. There's little point in declaring your own delegate when you can just use Action<string>, for example; using await helps quite a bit when dealing with mixed UI/non-UI code, as well as managing any kind of asynchronous code. You don't need to use event where a simple function will suffice - you can just pass that delegate to a function that needs it if that makes sense, and it may make perfect sense not to allow multiple event handlers to be called. If you decide to keep with the event, at least make sure it conforms to the EventHandler delegate.
To show how your code can be rewritten to be a bit more up-to-date and actually work:
void Main()
{
Application.Run(new LogForm());
}
public static class Log
{
public static async Task GenerateMessagesAsync(Action<string> logEvent,
CancellationToken cancel)
{
for (int i = 0; i < 1000; i++)
{
cancel.ThrowIfCancellationRequested();
logEvent(i.ToString());
await Task.Delay(1000, cancel);
}
}
}
public partial class LogForm : Form
{
private readonly List<string> messages;
private readonly Button btnStart;
private readonly Button btnStop;
private readonly TextBox tbxLog;
private readonly System.Windows.Forms.Timer timer;
public LogForm()
{
messages = new List<string>();
btnStart = new Button { Text = "Start" };
btnStart.Click += btnStart_Click;
Controls.Add(btnStart);
btnStop =
new Button { Text = "Stop", Location = new Point(80, 0), Enabled = false };
Controls.Add(btnStop);
tbxLog = new TextBox { Height = 200, Multiline = true, Dock = DockStyle.Bottom };
Controls.Add(tbxLog);
timer = new System.Windows.Forms.Timer { Interval = 500 };
timer.Tick += TimerProcessor;
timer.Start();
}
private void TimerProcessor(object sender, EventArgs e)
{
foreach (var message in messages)
{
tbxLog.AppendText(message + Environment.NewLine);
}
messages.Clear();
}
private async void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
var cts = new CancellationTokenSource();
EventHandler stopAction = (_, __) => cts.Cancel();
btnStop.Click += stopAction;
btnStop.Enabled = true;
try
{
await Log.GenerateMessagesAsync(message => messages.Add(message), cts.Token);
}
catch (TaskCanceledException)
{
messages.Add("Cancelled.");
}
finally
{
btnStart.Enabled = true;
btnStop.Click -= stopAction;
btnStop.Enabled = false;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
timer.Dispose();
btnStart.Dispose();
btnStop.Dispose();
tbxLog.Dispose();
}
base.Dispose(disposing);
}
}
SynchronizationContext.Send is run synchronously. When you call it, you actually block the UI thread until the operation is complete. But if UI thread is already in lock state, then it just make sense that you are in deadlock.
You can use SynchronizationContext.Post to avoid this.
I just answer on your question, but the truth is that your code need a "little" refactoring..
I would like to implement a simple popup window in Windows Forms, which will show a simple timer to the user while some slow-running process is executing. The premise is simple; show to the user that something is indeed going on and the application is not frozen. Note that this slow-running process is not a loop, nor is it something that I can tap into.
What I want is a simple popup window, showing some message along the lines "Elapsed time: x seconds", where x is incremented every second.
The basic concept is the following:
public void test()
{
//Some code which does stuff
//Popup window with counter
//Perform long running process
//Close popup window with counter
//Some other code which does other stuff
}
I tried to do it using various ways, including background workers, threads, and of course timers. But I did not manage to make it work as I wanted. And I would prefer not to post any of my code so as not to "lead" the responses to a specific way of doing this.
So what would be the best way to do this work?
Thanks.
UPDATE:
In reply to some comments, since I cannot paste any code in the replies section, I'm editing my original question to accomodate this. One of the implementations that I tried is to spawn the popup window in a separate thread. Although I got no runtime errors, the popup window did not refresh correctly. It indeed poped-up, but no text would show within it, and the counter would not refresh. Here's the code:
private void test()
{
frmProgressTimer ofrmProgressTimer = new frmProgressTimer(); //Instance of popup Form
System.Threading.Tasks.Task loadTask = new System.Threading.Tasks.Task(() => ProgressTimer(ofrmProgressTimer));
loadTask.Start();
//Perform long running process
System.Threading.Tasks.Task cwt = loadTask.ContinueWith(task => EndProgressTimer(ofrmProgressTimer));
}
private void ProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Show();
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.startTimer();
}));
}
private void EndProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
}
And here's my popup form code:
public partial class frmProgressTimer : Form
{
private int counter = 0;
private Timer timer1;
public frmProgressTimer()
{
InitializeComponent();
timer1 = new Timer();
timer1.Interval = 1000;
timer1.Tick += new EventHandler(timer1_Tick);
}
public void startTimer()
{
timer1.Start();
}
public void stopTimer()
{
timer1.Stop();
}
private void timer1_Tick(object sender, EventArgs e)
{
counter += 1;
labelText.Text = counter.ToString();
}
}
This is actually quite easy to do. Create your dialog, define your long running operation to take place in a non-UI thread when it is shown, add a continuation to that operation which closes the dialog when the task finishes, and then show the dialog.
MyDialog dialog = new MyDialog();
dialog.Shown += async (sender, args) =>
{
await Task.Run(() => DoLongRunningWork());
dialog.Close();
};
dialog.ShowDialog();
The code to have the ticking over time should be entirely contained within the dialog, and based on the question it seems you already have that well under control with a simple Timer.
Make a new form, which will pop up, and show a timer. That way it won't be interrupted with all the work on your main form, and the timer will work continuously.
Remember when showing a new from to use newForm.ShowDialog() not newForm.Show(). Your can google the differences
I would simply start your work on a separate thread. Launch a modal form with your timer output. To display the timer use an actual timer instance set to update every second. When the timer event fire update your dialog.
Finally once you're thread completes close the dialog so your main form is active again.
First of all you need to make it not closeable by the user (as if modal dialogs weren't annoying enough) but closeable by your code. You could accomplish this by subscribing to the FormClosing event of the form. Let's say your popup form's name is Form2:
private bool mayClose = false;
public void PerformClose()
{
this.mayClose = true;
this.Close();
}
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (!this.mayClose)
e.Cancel = true;
}
Create a Timer, provide a Tick event handler, enable it and set its Interval to 500 milliseconds:
Create a label to host your desired text. Let's call it label1.
Within and surrounding your Tick event handler do something like this:
private DateTime appearedAt = DateTime.UtcNow;
private void timer1_Tick(object sender, EventArgs e)
{
int seconds = (int)(DateTime.UtcNow - this.appearedAt).TotalSeconds;
this.label1.Text = string.Format(#"Ellapsed seconds: {0}", seconds);
}
Make sure your long running process is happening on a background thread, not on the GUI thread.
Say your long running process can be thought of as the execution of a method called MyProcess.
If that is the case, then you need to call that method from a secondary thread.
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
Show the form right before the long running process starts. This can be done in any of the 2 places (marked in the previous section of code) called PLACE1 or PLACE2. If you do it in PLACE2 you will have to marshal a call back to the GUI thread in order to be able to interact with the WinForms framework safely. Why am I bringing this up ? It's because maybe the long running process is not started from within the GUI thread at all and you absolutely need to do this.
Close the form right after the long running process finishes. This can be done only in PLACE3 and you absolutely need to marshal a call.
To wrap the earlier 2 bullets and the answer, you could do this:
private void DoIt()
{
Form2 form2 = new Form2();
Action showIt = () => form2.Show();
Action closeIt = () => form2.PerformClose();
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
form2.BeginInvoke(showIt);
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
form2.BeginInvoke(closeIt);
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
}
Finally I've managed to resolve this in the most simplistic manner. And it works like a charm. Here's how to do it:
//Create an instance of the popup window
frmProgressTimer ofrmProgressTimer = new frmProgressTimer();
Thread thread = new Thread(() =>
{
ofrmProgressTimer.startTimer();
ofrmProgressTimer.ShowDialog();
});
thread.Start();
//Perform long running process
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
You can see the code for the popup window in the original post/question, with the only difference that the tick function changes the label text as:
labelText.Text = string.Format("Elapsed Time: {0} seconds.", counter.ToString());
Thank you to everybody for trying to help me out.
I know there are a lot of similar questions out there and I have read through a lot of them. Unfortunately, I still couldn't solve my problem after reading through them - however I am relatively new to C#. According to docs the problem of not being thread safe results in an InvalidOperationException when using the debugger. This is not the case in my problem.
I've recreated the problem with a simple raw test class to concentrate on my problem.
The main form is supposed to show a kind of a progress dialog.
public partial class ImportStatusDialog : Form
{
public ImportStatusDialog()
{
InitializeComponent();
}
public void updateFileStatus(string path)
{
t_filename.Text = path;
}
public void updatePrintStatus()
{
t_printed.Text = "sent to printer";
}
public void updateImportStatus(string clientName)
{
t_client.Text = clientName;
}
public void updateArchiveStatus()
{
t_archived.Text = "archived";
}
}
When that code is called without any Invoke() from the main form:
private void button1_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
nDialog.updateFileStatus("test");
Thread.Sleep(1000);
nDialog.updateImportStatus("TestClient");
Thread.Sleep(1000);
nDialog.updatePrintStatus();
Thread.Sleep(1000);
nDialog.updateArchiveStatus();
Thread.Sleep(1000);
nDialog.Close();
}
And even when I call it like this:
private void button3_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateFileStatus("Test");
});
}
else
{
nDialog.updateFileStatus("Test");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updatePrintStatus();
});
}
else
{
nDialog.updatePrintStatus();
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateImportStatus("cName");
});
}
else
{
nDialog.updateImportStatus("cName");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateArchiveStatus();
});
}
else
{
nDialog.updateArchiveStatus();
}
Thread.Sleep(1000);
nDialog.Close();
}
the dialog which looks like this in the designer (in my example)
will be displayed like that:
When I use ShowDialog() instead of Show() the dialog displays correnctly, but as the API Doc points out
You can use this method to display a modal dialog box in your
application. When this method is called, the code following it is not
executed until after the dialog box is closed
which is not what I want, especially as it would mean that the dialog updating would only happen after it has been closed again.
What am I doing wrong here? This seems to be a trivial problem and yet the solution evades me. Please keep in mind that I am new to C# GUI programming.
Also, I would like to ask what would be the right place for using the Invoke()? Do Would you use it in the main form when calling the dialog methods or rather in the dialog update methods itself?
It doesn't look like you're using multiple threads here, so the invoke stuff is not required. Invoke is only needed for cross-thread calls - you are creating multiple forms, but all your code is running in the same thread.
If you're doing your work in the main UI thread (as implied by the code being in a button click event), then just call Application.DoEvents() periodically to allow the progress form to refresh.
A better solution would be to use a BackgroundWorker for your work, and have it report its progress periodically.
Then you can update the progress form in the BackgroundWorker's ProgressChanged event (which will be executed in the main thread, so you still don't have to invoke anything).
Background
From the valuable advice I received here I have now moved all of my database intensive code to a backgroundworker, specifically the direct calls to the database. That code is executed during the backgroundworker's DoWork event. If a DataTable is returned during the DoWork event, I set that DataTable to a class-wide variable. This is done, to avoid having to invoke the controls requiring the DataTable every time I run this code.
While that code is being executed, I have a label that is updated in the main UI thread, to let the user know that something is occurring. To update the label I use a timer, such that every 750 ms a "." is appended to the label's string.
The first thing that I noticed was that the backgroundworker's RunWorkerCompleted event wasn't triggering. To solve this I did an Application.DoEvents(); before each call I made to the backgroundworker. It was ugly, but it caused the event to trigger. If anyone has an alternative to fix this, I am all ears.
I then came across an interesting predicament. If I run the program within Visual Studio 2010, in the debugging mode, I get an InvalidOperationException error stating that the "Cross-thread operation not valid: Control 'lblStatus' accessed from a thread other than the thread it was created on." This error occurs during the backgroundworker's RunWorkerCompleted event, where I set the text of a label in the main UI thread. But, when I launch the application directly, through the executable, it works exactly as desired (i.e. the label's text is set correctly).
Question
Can anyone explain what is going on / offer advice on how to improve upon this?
Code
I can't post all of the code involved, but here's some relevant stuff:
namespace Test
{
public partial class frmMain : Form
{
public static Boolean bStatus = false;
static Boolean bTimer = false;
System.Timers.Timer MyTimer = new System.Timers.Timer();
public frmMain()
{
InitializeComponent();
MyTimer.Elapsed += new System.Timers.ElapsedEventHandler(MyTimer_Elapsed);
MyTimer.Interval = 750; // Every 3/4 of a second
ExampleTrigger();
}
/// <Insert>Lots of unshown code here</Insert>
private void ExampleTrigger()
{
// This is used to simulate an event that would require the backgroundworker
Application.DoEvents();
bgw.RunWorkerAsync(0);
WaitText("Example - 1");
}
private static void MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
bTimer = true;
}
// Update status text
private void WaitText(string txt)
{
MyTimer.Enabled = true;
lblStatus.Text = txt;
bStatus = false;
while (!bStatus)
{
if (bTimer)
{
txt = txt + ".";
lblStatus.Text = txt;
lblStatus.Update();
bTimer = false;
}
}
MyTimer.Enabled = false;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
int iSelect = (int)e.Argument;
switch (iSelect)
{
case 0:
// Hit the database
break;
/// <Insert>Other cases here</Insert>
default:
// Do something magical!
break;
}
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bStatus = true;
lblStatus.Text = "Ready!"; // This is where the exception occurs!
}
}
}
Never run a while() loop like that in the UI thread.
You're freezing the UI until the loop terminates; this defeats the purpose.
In addition, System.Timers.Timer doesn't run callbacks in the UI thread.
Use a WinForms Timer instead.
Once you switch to a WinForms timer, you can simply append to the label inside the timer callback, and disable the timer when the operation finishes.
So I have two event handlers button1_Click() and button2_Click()
In button1_Click() I have something running like this:
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
//DoStuff
}
But button2_Click is supposed to be a stop button, and stop button1 early.
How do I go about this?
Thanks for the help. I saw this article here about it, but couldn't get it to work.
Windows.Forms answer
The least sophisticated method is this:
private bool m_stop;
private void button1_Click (object s, EventArgs ea)
{
try
{
// Don't forget to disable all controls except the ones you want a user to be able to click while your method executes.
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
Application.DoEvents ();
// DoEvents lets other events fire. When they are done, resume.
if (m_stop)
break;
//DoStuff
}
finally
{
// Enable the controls you disabled before.
}
}
private void button2_Click (object s, EventArgs ea)
{
m_stop = true;
}
It has the distinct advantage of letting you execute button1_Click on the UI thread, still lets the UI respond to your stop button.
It has a disadvantage that you must protect against reentrancy. What happens if they click your button1 while button1_click is already executing!?!?
Edit: Another way I have used is to use a Timer instead of a loop. Then, the stop method just stops the timer.
As much as I understood, correct me if I'm wrong, you're on single thread.
Wired, but you can check for single boolean value inside the your While loop, just as post suggested.
May be to make life easier (may be this is what "couldn't get it to work" means) is inside loop call
1) Windows Forms: Application.DoEvents()
2) WPF (little bit more tricky) : DoEvents in WPF
This to make breathe system.
You need to start the process inside the button1 in new thread, and when you press the button2 flag a local variable to false to stop the loop. like:
using System.Threading;
private volatile bool _requestStop = false;
private readonly object _oneExecuteLocker = new object();
private void OnButton1Click(ojbect sender, EventArgs e)
{
new Thread(() =>
{
if (Monitor.TryEnter(_oneExecuteLocker))
{//if we are here that is means the code is not already running..
try
{
while (!_requestStop)
{
//DoStuff
}
}
finally
{
Monitor.Exit(_oneExecuteLocker);
}
}
}){ IsBackground = true }.Start();
}
private void OnButton2Click(object sender, EventArgs e)
{
_requestStop = true;
}
Notes:
When ever you want to update a UI control inside the newly created thread you should use contorl.Invoke(/*the code get/set or call method in the UI*/).
The Monitro.Enter is just to be sure that your code will not executed multiple time per click if it already running.