I am developing a skype-like application, I have an external DLL that do most of the work and fires events handled in my class ip2ip, one of this events is incoming_call fired when there is an incoming call as the name suggest. I'm trying to manage missed calls.
Now this is the relevant part of the code in this class:
private void ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
{
if (Calling)
{
ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
}
else
{
AcceptIncomingCall = null;
UserCaller = FindUserName(callbackipaddress);
IncomingCall = true;
//waiting for the call to be accepted from outside of this class
while (AcceptIncomingCall.HasValue == false) Thread.Sleep(100);
if(AcceptIncomingCall.Value == true)
{
//call back to have a 1 on one video conference
icc.Parent.BeginInvoke(new MethodInvoker(delegate
{
//accept the incoming call
ics.AcceptCall("n/a", socketHandle);
icc.Call(callbackipaddress, callbackvideoport, 0, 0,
"n/a", callbackid,
ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");
Calling = true;
}));
}
else
{
ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
Log = "Incoming call not accepted";
Calling = false;
}
AcceptIncomingCall = null;
IncomingCall = false;
}
}
IncomingCall is a property generating a PropertyChangedEvent, wich is captured in my main class where I have this code:
private void ip2ip_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e != null && string.IsNullOrEmpty(e.PropertyName) == false)
{
..............
if (e.PropertyName.Equals("IncomingCall") && ip2ip.IncomingCall == true)
{
Invoke(new MethodInvoker(delegate
{
pnlCalling.Visible = true;
aTimer.Start();
}));
}
................
}
}
public Form1()
{
.......
aTimer = new System.Windows.Forms.Timer();
aTimer.Interval = 10000;
aTimer.Tick += aTimer_Tick;
}
void aTimer_Tick(object sender, EventArgs e)
{
aTimer.Stop();
btnNo.PerformClick();
}
private void btnNo_Click(object sender, EventArgs e)
{
aTimer.Stop();
ip2ip.AcceptIncomingCall = false;
}
private void btnOk_Click(object sender, EventArgs e)
{
aTimer.Stop();
ip2ip.AcceptIncomingCall = true;
}
I need the timer to manage the missed call, when there is an incoming call a panel appears, with buttons to accept/reject the call. If the user waits too much the call is considered rejected (missed).
In this way it doesn't work, probably I'm doing something wrong with the timer, as without any timer everything works. I also tried the timer of the class System.Timers with same results. Any Idea?
EDIT
This is my expectation, there is an incoming call so the event ics_IncomingCall is fired, IncomingCall=true cause the execution to go to the main class (we are still in same thread, I see it debugging step by step in VS) where is invoked in the GUI thread the panel to be visible and started the timer, now we have one thread where a while loop block the execution until in the other thread user do something (accept/reject).
The problem exist when the user accept the call, the code after the while loop is always executed, the caller has no problem at all and receive the stream, but in the receiver (who receive the stream as I verified in wireshark) the DLL (who is responsible to show the incoming video) fails to do its job for some reason unknown to me but caused by the timer.
It is unfortunate your question does not include a good, minimal, complete code example that reliably reproduces the problem. Having such a code example would make it much more practical for someone to provide a useful answer.
That said, as explained by commenter varocarbas, your fundamental problem appears to be that you have blocked the UI thread (with the while loop), while at the same time hoping for the UI thread to handle other activity (such as the timer's tick event). In fact, you are also preventing the button click from having an effect. The button Click event handlers can't execute either, while the UI thread is blocked.
One possible way to fix this would be to use a TaskCompletionSource<T> to provide the ics_IncomingCall() with a waitable object, which the buttons and timer can use to signal. For example:
// Change from "bool?" to this:
private TaskCompletionSource<bool> AcceptIncomingCall;
public void HandleCall(bool accept)
{
AcceptIncomingCall.SetResult(accept);
}
private async Task ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
{
if (Calling)
{
ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
}
else
{
AcceptIncomingCall = new TaskCompletionSource<bool>();
UserCaller = FindUserName(callbackipaddress);
IncomingCall = true;
//waiting for the call to be accepted from outside of this class
if (await AcceptIncomingCall.Task)
{
//call back to have a 1 on one video conference
icc.Parent.BeginInvoke(new MethodInvoker(delegate
{
//accept the incoming call
ics.AcceptCall("n/a", socketHandle);
icc.Call(callbackipaddress, callbackvideoport, 0, 0,
"n/a", callbackid,
ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");
Calling = true;
}));
}
else
{
ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
Log = "Incoming call not accepted";
Calling = false;
}
AcceptIncomingCall.Dispose();
IncomingCall = false;
}
}
and:
void aTimer_Tick(object sender, EventArgs e)
{
aTimer.Stop();
btnNo.PerformClick();
}
private void btnNo_Click(object sender, EventArgs e)
{
aTimer.Stop();
genericServerClient.HandleCall(false);
}
private void btnOk_Click(object sender, EventArgs e)
{
aTimer.Stop();
genericServerClient.HandleCall(false);
}
This causes the ics_IncomingCall() method to return when it reaches the await statement, allowing its thread to continue executing. The button Click event handlers will call back to the public method that encapsulates your field (public fields are very dangerous and should be avoided in almost all situations), setting the result value for the TaskCompletionSource object that is being awaited.
Once the result value has been set, this will cause the framework to resume executing your ics_IncomingCall() method where it left off, but now with the value returned from the button Click event handlers. I.e. true if the user clicked the btnOk and false if they clicked btnNo or the timer interval elapsed.
Note that this changes the signature of your ics_IncomingCall() method, which will force a change to the caller. The best way to handle that will be to change the caller as well, to be async and to use await ics_IncomingCall(...). That will of course force a change in its caller, and its caller's caller, and so on. But you need to release the UI thread, and this is the best way to do it. Hopefully you don't have a lot of callers to change, but even if you do, this is the way to go.
If the above does not seem to address your problem, please provide a good MCVE. Note that a good MCVE is both complete and minimal. You will want to remove from the example any code that is not strictly required to reproduce the problem. At the same time, make sure someone can copy and paste the code into an empty project and have it run with at most very minimal effort, and preferably none at all.
Related
I'm using a Background worker to read values in and to pass values to Worker_ProgressChanged, to update UI.
In Worker_DoWork:
while (agi.DvmReadyToRead) // wait for digipot to be adjusted before reading in worker
{
Thread.Sleep(20);
Application.DoEvents();
//logS.Debug("Waiting for ready to read in worker");
}
Thread.Sleep(40); // Give digipot chance to make the change
agi.SendSoftwareTriggerOne();
Thread.Sleep(7); // Duration for above command to execute
A = agi.ReadOne();
Thread.Sleep(1);
agi.InitOne();
Thread.Sleep(1);
sAndH3 = A[0];
worker.ReportProgress(0, new System.Tuple<double>(sAndH3));
agi.DvmReadyToRead = true;
In Worker_ProgressChanged:
while (!agi.DvmReadyToRead)
{
//logS.Debug("waiting for ready to read in progress");
Thread.Sleep(0);
Thread.Sleep(0);
Thread.Sleep(0);
Thread.Sleep(0);
Thread.Sleep(0);
Application.DoEvents(); // Exception thown here
Thread.Sleep(1); // wait for DVM reading
}
agi.DvmReadyToRead = false;
// Then goes on to adjust output voltage up or down
This is working fine the first time round using
Application.DoEvents();
however after first run, I get a stackoverflow at this point. After reading many posts on here DoEvents is not the best way of doing what I am trying to achieve.
So what I would like is a way to pass a Boolean back to DoWork, or another way to allow worker to be able to read the agi.DvmReadyToRead Boolean.
Thanks!
If I understand your question, you are describing a very common pattern in Test and Measurement where you have an instrument that takes some time after triggering it before it gets a reading. But you want to know when the reading happens so that you can take some action (like update a ProgressBar or TextBox perhaps) and you want be able to cancel the worker loop.
When I need to do this myself, I like to use the System.Threading.Tasks to simplify things. I'll post a complete pattern here in the hope that you can find something of use to solve the issue you are having.
To be clear, I am trying to answer your question of "a way to pass a Boolean back to DoWork..." by saying that one way to do this is to fire an Event from Worker_DoWork that can contain Boolean (like you asked) or double (in my example) or any other information you choose.
Good luck!
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace StackOverflow02
{
public partial class DVMLoopRunner : Form
{
public DVMLoopRunner()
{
InitializeComponent();
DVMReadingAvailable += Form1_DVMReadingAvailable;
ContinueOrCancel += Form1_ContinueOrCancel;
}
// See if User has turned off the Run button then cancel worker
private void Form1_ContinueOrCancel(Object sender, CancelEventArgs e)
{
e.Cancel = !checkBoxRunMeterLoop.Checked;
}
// The DVM, after being triggered + some delay, has come up with a new reading.
private void Form1_DVMReadingAvailable(Object sender, DVMReadingAvailableEventArgs e)
{
// To update GUI from worker thread requires Invoke to prevent Cross-Thread Exception
Invoke((MethodInvoker)delegate
{
textBox1.Text = e.Reading.ToString("F4");
});
}
// Make our events so that we can be notified of things that occur
public event CancelEventHandler ContinueOrCancel;
public event DVMReadingAvailableEventHandler DVMReadingAvailable;
// This is how we will provide info to the GUI about the new reading
public delegate void DVMReadingAvailableEventHandler(Object sender, DVMReadingAvailableEventArgs e);
public class DVMReadingAvailableEventArgs : EventArgs
{
public readonly double Reading;
public DVMReadingAvailableEventArgs(double reading)
{
Reading = reading;
}
}
// When the User checks the box, Run the worker loop
private void checkBoxRunMeterLoop_CheckedChanged(Object sender, EventArgs e)
{
if(checkBoxRunMeterLoop.Checked)
{
Task.Run(() => ReadDVMWorker());
}
}
// Worker Loop
private void ReadDVMWorker()
{
while(true)
{
CancelEventArgs e = new CancelEventArgs();
ContinueOrCancel?.Invoke(this, e);
if (e.Cancel) return; // If User has turned off the Run button then stop worker
ReadDVM(); // This worker thread will block on this. So trigger, wait, etc.
}
}
// DVM Takes some period of time after trigger
void ReadDVM()
{
Thread.Sleep(1000);
double newSimulatedReading = 4.5 + Random.NextDouble();
DVMReadingAvailable?.Invoke(this, new DVMReadingAvailableEventArgs(newSimulatedReading));
}
Random Random = new Random(); // Generate random readings for simulation
}
}
I've got one form called Sorter. There is the button 'jademy' on it which opens window 'Progress Window'
private void jademy_Click(object sender, EventArgs e)
{
ProgressWindow progress = new ProgressWindow();
progress.ShowDialog();
}
Code of 'Progress Window' form is following:
public partial class ProgressWindow : Form
{
private BackgroundWorker backgroundWorker = new BackgroundWorker();
public ProgressWindow()
{
InitializeComponent();
stop.Visible = true;
ok.Visible = false;
backgroundWorker.RunWorkerAsync();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
#region block1
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
#endregion
}
private void stop_Click(object sender, EventArgs e)
{
backgroundWorker.CancelAsync();
}
private void ok_Click(object sender, EventArgs e)
{
this.Close();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
backgroundWorker.ReportProgress(i);
}
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
this.Text = "Done: " + e.ProgressPercentage.ToString() + "%";
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
MessageBox.Show("Cancelled", "Message", MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk);
}
else if (!(e.Error == null))
{
MessageBox.Show("Error: " + e.Error.Message, "ERROR!", MessageBoxButtons.OKCancel);
}
else
{
ok.Visible = true;
stop.Visible = false;
}
}
}
Now. I have three problems.
Click on stop button does nothing. It seems that 'backgroundWorker.CancelAsync()' doesn't stop the process.
When I close progress window and I want to run it again I have to wait some time before click on 'jademy' button. Otherwise progress window is displayed like this:
(and nothing changes) instead of this: It looks like the program "remembers" that work was done even though it is a new instance of ProgressWindow. Notice that on the incorrect version 'OK' button is visible at once - instead of waiting for the completion of the work.
I would like to clarify the code in "block 1". To be honest I don't understand it fully. Is this part really essential or not? I mean, I've found a lot of examples (also on this forum - e.g. here), where this part wasn't included and users were reporting that the solution works. In my case, without this part progress bar didn't work at all, but maybe I've done something wrong.
Calling CancelAsync stops any pending work. But if the work has already started, the method body needs to check if cancel was called. See CancelAsync
CancelAsync submits a request to terminate the pending background
operation and sets the CancellationPending property to true.
When you call CancelAsync, your worker method has an opportunity to
stop its execution and exit. The worker code should periodically check
the CancellationPending property to see if it has been set to true.
I have no idea about it. By the way the images do not work. Embed it in the question.
The code assigns a method that is executed when the BackgroundWorker starts and you hook up methods to report the progress and do cleanup / updates once the background work is complete.
BackgroundWorker.CancelAsync is often misunderstood. It does not stop any pending work but is merely a signal to the UI thread that the work has been canceled! It just sets the CancellationPending property, which you can poll in the DoWork regularly.
Unfortunately the MSDN example with the Thread.Sleep calls in the DoWork is a very silly one. Normally you call a blocking operation in DoWork, which is often completely UI-independent.
See my answer here for a more usable example.
1.
According to MSDN BackgroundWorker Class page, maybe you should add a break to the loop.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}
2.
Have no idea.
3.
The block 1 region is setting for BackgroundWorker event. In my case , it normally will appear at Form1.Designer.cs if I click the lightning icon in attribute to set the event.
Why does my first attempt to change a button's text in this code not work, while the third attempt does work ?
My user will have to wait a few seconds while the serial port connects. After that, I want to alert him that he has already connected (a second attempt can cause problems).
I wanted to let him know that things are okay, so he won't think "duh" and click twice.
Fail. The text change never appears.
Okay, why does the third change in button text work, but the first one does not ? I don't know if the second one works or not.
/***********************************************************************
* Button: Connect Port *
***********************************************************************/
private void TheConnectPortButton_Click(object sender, EventArgs e)
{
string OldText = TheConnectPortButton.Text;
TheConnectPortButton.Text = "Busy, Please Wait"; /////// This never happens
ButtonBoss.ButtonHandler_ConnectPort();
TheConnectPortButton.Text = OldText; /////// Not sure if this happens
if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
{
TheConnectPortButton.Text = "Connected"; /////// This one does happen
}
}
the aUartSemaphoreThatTells.IfWeHaveConnectedToAPort is also used by the ButtonBoss routine to make sure he doesn't connect a second time, along with other button routines (e.g., make sure we are connected before we Tx/Rx or whatever).
I tried changing the code after the routine returns to look like this...
if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
{
TheConnectPortButton.Text = "Connected";
}
else
{
TheConnectPortButton.Text = OldText;
}
...and I still get the same result.
My guess (and that's all it is) is that threading is somehow involved in all this, and that the serial port routines trump the button text changing routines by some convolution that I don't follow properly at the moment.
Question: What do I need to do to get the text to change before the connection stuff hogs the system ?
(If that's what's happening)
Question 2: If I can't make this happen, I think I've read about "greying out" the buttons, or, I believe I saw somewhere that I can actually make a button go away right before the user's eyes so that he can't click it again. Links to example code would be welcome.
The problem is you're doing everything from one and the same event-handler consequently, so that the button has no time to get updated (redrawn). You could call Application.DoEvents(); method, but it's not a good idea at all, please, read Use of Application.DoEvents()
I think usually you're expected to push a time-consuming task into a separate thread, get progress report from it and update your GUI. There is a plenty of ways to create a "worker" thread and get some respond from it. For example, use a BackgroundWorker Class:
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker w = new BackgroundWorker();
w.WorkerReportsProgress = true;
w.DoWork += new DoWorkEventHandler(w_DoWork);
w.ProgressChanged += new ProgressChangedEventHandler(w_ProgressChanged);
w.RunWorkerCompleted += new RunWorkerCompletedEventHandler(w_RunWorkerCompleted);
w.RunWorkerAsync();
button1.Text = "Started";
}
//may influence GUI, as this event handler is run on the GUI thread
void w_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Text = "Job is done";
}
//may influence GUI, as this event handler is run on the GUI thread
void w_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
button1.Text = e.ProgressPercentage.ToString();
}
//runs in the worker thread...should do the actual job
//may influence GUI through `ReportProgress`
//or through `Invoke` method
void w_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
Thread.Sleep(500);
worker.ReportProgress(10 * i);
}
}
}
Or you may use Task Class:
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e)
{
new Task(DoTask).Start();
}
void DoTask()
{
for (int i = 1; i <= 10; i++)
{
Thread.Sleep(500);
//here you need to update GUI through `Invoke` method
//as the GUI may only be influenced from the the thread,
//where it's created
this.Invoke(new Action<int>((j) =>
{
button1.Text = j.ToString();
}), 10 * i);
}
}
}
When a user clicks on Run, the application runs through a lot of code to generate a model and display it in a Chart. The Run takes about 1-2 minutes to run. I also have a Cancel button that gets enabled after the Run button is clicked. I am working with DotSpatial, so my buttons are on a plugin panel in a ribbon UI. The click event on the Run and Cancel start in the plugin, which calls the back-end class's code Run and Click.
When the user hits cancel after the run starts, there is a delay, but the cancel method is invokes and executes, but the run never stops and we eventually see the chart display. So, I'm thinking I need a separate thread for the Run. I'm fairly new to programming, and never worked with Threading. I've looked into it and added the below code, but my thread method isn't running. Here's my code:
The Run button is clicked:
This is at the top:
//check to see if RunModel thread needs to stop or continue
private volatile bool stopRun = false;
private Thread runThread;
Then this is the method that's called from the click event:
public void btnRun_testingThread(object sender, EventArgs e)
{
//create a new thread to run the RunModel
if (runThread == null)
{
//we don't want to stop this thread
stopRun = false;
runThread = new Thread(RunModel);
runThread.Start(); <--this isn't doing anything
}
So, I would think that when the code gets to the runThread.Start(), it would jump into my RunModel method and start running through the code. But it doesn't. Additionally, I'll want to cancel out of this thread (once I have it working correctly), so I have this, which gets called from the cancel click method:
private void StopRunThread()
{
if (runThread != null)
{
//we want to stop the thread
stopRun = true;
//gracefully pause until the thread exits
runThread.Join();
runThread = null;
}
}
Then the this is the RunModel() where I'm checking occasionally to see if the stopRun bool has changed.
public void RunModel()
{
...some code.....
//check to see if cancel was clicked
if (stopRun)
{
....clean up code....
return;
}
....some more code....
//check to see if cancel was clicked
if (stopRun)
{
....clean up code....
return;
}
}
And the cancel button click method:
public void btnCancel_Click(Object sender, EventArgs e)
{
stopRun = true;
StopRunThread();
//the model run has been canceled
....some code.....
}
Any help on getting the thread.start to actually run the Run method? Then do I need to constantly check the volatile bool in the run in order to clean everything up if it's being stopped? Thanks!
I think you'd be best looking at the BackgroundWorker - this essentially runs separately but can watch out for cancellation commands. Make sure you add 'WorkerSupportCancellation' when you initialise it:
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork); // This does the job ...
backgroundWorker1.WorkerSupportsCancellation = true; // This allows cancellation.
Then on click you can start your process:
public void btnRun_testingThread(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
Your cancel button can issue a cancellation request:
public void btnCancel_Click(Object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
Then your worker can monitor for this as it's doing it's work ...
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
if (backgroundWorker1.CancellationPending)
{
break;
}
else
{
// Do whatever you're doing.
}
}
e.Result = backgroundWorker1.CancellationPending ? null : orders;
}
You can enhance this further by adding progress bars etc., but that gets a bit more complicated so I won't go into it here.
Considering new info provided in commend I believe you just missed a start of the RunModel() method in debugger because of wrong assumption regarding thread.Start() method behaviour.
Please see a note from MSDN, Thread.Start Method
Once a thread is in the ThreadState.Running state, the operating
system can schedule it for execution. The thread begins executing
at the first line of the method represented by the ThreadStart or
ParameterizedThreadStart delegate supplied to the thread constructor.
Small demonstration that thread start takes some time bits, for me it starts in 38-40 milliseconds:
Stopwatch watch = new Stopwatch();
Thread thread = new Thread((ThreadStart)watch.Stop);
thread.Start();
watch.Start();
Thread.Sleep(5000);
double startedAfter = watch.ElapsedMilliseconds;
Since .NET Framework 4.0 consider using TPL Tasks rather than threads explicitly, some pros:
You can easily synchronize with UI thread by passing in a Task UI Thread synchronization context
You can easily stop a Taks using CancellationToken
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.