How to pause inside a loop without using Thread.Sleep? - c#

using Thread.Sleep I manage to make a pause inside a loop but it has the disadvantage of freezing my program while the pause lasts. The purpose of my program is to start a loop when a button is clicked and to stop this loop when another button is clicked. Here is the code I wrote:
private void startRPLoop_Click(object sender, EventArgs e)
{
timer1.Interval = 1000;
timer1.Enabled = true;
}
private void stopRPLoop_Click(object sender, EventArgs e)
{
timer1.Interval = 1000;
timer1.Enabled = false;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (timer1.Enabled == true)
{
GlobalRPValue = 500;
WantedLevel = 1;
Thread.Sleep(1000);
WantedLevel = 0;
Thread.Sleep(1000);
}
else
{
GlobalRPValue = 1;
WantedLevel = 0;
}
}
I thought of creating a Task so I could use await Task.Delay(); which will allow me to start the loop and make pauses without my program being suspended because of Thread.Sleep but I don't know how to go about it.
I hope I have been precise enough because I am new to C# and thank you for your help :)

Your question is How to pause inside a loop without using Thread.Sleep?. You posted some sample code that uses System.Windows.Forms.Timer but when I worked it out using Timer it required more complicated code. This answer proposes a simpler solution that (based on our conversation) does what you want without using Timer. It runs a loop when the button is clicked and toggles the WantedLevel between 0 and 1 once per second without freezing your UI.
The "Button" is actually a checkbox with Appearance=Button. Clicking it will toggle the checked state and when toggled on, it starts a loop. First, the onTick method sets WantedLevel to 1 for a duration of 1 second before returning. Then it will await an additional 1-second delay before repeating the process.
CancellationTokenSource _cts = null;
private async void checkBoxLoop_CheckedChanged(object sender, EventArgs e)
{
if(checkBoxLoop.Checked)
{
labelGlobalRPValue.Text = "GlobalRPValue=500";
textBoxConsole.Text = $"{DateTime.Now.ToString(#"mm:ss")}: Start clicked{Environment.NewLine}";
textBoxConsole.AppendText($"{DateTime.Now.ToString(#"mm:ss")}: {labelGlobalRPValue.Text} {Environment.NewLine}");
_cts = new CancellationTokenSource();
while (checkBoxLoop.Checked)
{
try {
await onTick(_cts.Token);
await Task.Delay(1000, _cts.Token);
}
catch(TaskCanceledException)
{
break;
}
}
ResetDefaults();
}
else
{
textBoxConsole.AppendText($"{DateTime.Now.ToString(#"mm:ss")}: Stop clicked{Environment.NewLine}");
_cts?.Cancel();
}
}
The onTick handler is marked async which allows the Task.Delay to be awaited. Other than that it's quite simple and tries to follow the essence of the handler you posted.
private async Task onTick(CancellationToken token)
{
labelWantedLevel.Text = "WantedLevel=1";
textBoxConsole.AppendText($"{DateTime.Now.ToString(#"mm:ss")}: {labelWantedLevel.Text} {Environment.NewLine}");
await Task.Delay(1000, token);
labelWantedLevel.Text = "WantedLevel=0";
textBoxConsole.AppendText($"{DateTime.Now.ToString(#"mm:ss")}: {labelWantedLevel.Text} {Environment.NewLine}");
}
When the checkbox state toggles off, it cancels the current Task.Delay using the CancellationTokenSource which causes the loop to exit. The ResetDefaults() method is called to restore default values for WantedLevel and GlobalRPValue.
private void ResetDefaults()
{
labelGlobalRPValue.Text = "GlobalRPValue=1";
labelWantedLevel.Text = "WantedLevel=0";
textBoxConsole.AppendText($"{DateTime.Now.ToString(#"mm:ss")}: Cancelled (reset defaults) {Environment.NewLine}");
textBoxConsole.AppendText($"{labelGlobalRPValue.Text} {Environment.NewLine}");
textBoxConsole.AppendText($"{labelWantedLevel.Text} {Environment.NewLine}");
}
EDITS TO CONFORM TO ORIGINAL POST PER COMMENT
Handle Buttons
private bool _checkBoxLoop_Checked = false;
private void startRPLoop_Click(object sender, EventArgs e)
{
_checkBoxLoop_Checked = true;
checkBoxLoop_CheckedChanged(sender, e);
}
private void stopRPLoop_Click(object sender, EventArgs e)
{
_checkBoxLoop_Checked = false;
checkBoxLoop_CheckedChanged(sender, e);
}
Enable/Disable buttons for operational safety
private async void checkBoxLoop_CheckedChanged(object sender, EventArgs e)
{
stopRPLoop.Enabled = _checkBoxLoop_Checked; // Added
startRPLoop.Enabled = !stopRPLoop.Enabled; // Added
if (_checkBoxLoop_Checked) // use member variable instead of checkbox state
{
labelGlobalRPValue.Text = "GlobalRPValue=500";
textBoxConsole.Text = $"{DateTime.Now.ToString(#"mm:ss")}: Start clicked{Environment.NewLine}";
textBoxConsole.AppendText($"{DateTime.Now.ToString(#"mm:ss")}: {labelGlobalRPValue.Text} {Environment.NewLine}");
_cts = new CancellationTokenSource();
while (_checkBoxLoop_Checked)
{
try {
await onTick(_cts.Token);
await Task.Delay(1000, _cts.Token);
}
catch(TaskCanceledException)
{
break;
}
}
ResetDefaults();
}
else
{
textBoxConsole.AppendText($"{DateTime.Now.ToString(#"mm:ss")}: Stop clicked{Environment.NewLine}");
_cts?.Cancel();
}
}

Related

How can I stop do-while in windows form app by clicking on a button?

I want to use a button in windows form app to stop do-while. When I click on stop button, the stop button does not work. How can I do it?
do{
…….
} while (stop == 0);
In Stop button:
stop = 1;
You can run your loop in a Task or async void, and use a CancellationToken to stop it
Given
private static CancellationTokenSource _cs;
private static CancellationToken _token;
public static async Task StartWhile()
{
while (!_token.IsCancellationRequested)
{
Console.WriteLine("looping");
await Task.Delay(1000);
}
Console.WriteLine("loop stopped");
}
Usage
_cs = new CancellationTokenSource();
_token = _cs.Token;
Task.Run(StartWhile);
Console.WriteLine("Press any key to exit");
var result = Console.ReadKey();
Console.WriteLine("You Pressed "+ result.Key);
_cs.Cancel();
Console.WriteLine("loop stopped");
Console.ReadKey();
Output
Press any key to exit
looping
looping
looping
looping
You Pressed Enter
loop stopped
Or a Winforms example
private async void btnStart_Click(object sender, EventArgs e)
{
try
{
btnStart.Enabled = false;
_cs = new CancellationTokenSource();
_token = _cs.Token;
while (!_token.IsCancellationRequested)
{
Debug.WriteLine("looping");
await Task.Delay(1000);
}
Debug.WriteLine("loop stopped");
}
finally
{
btnStart.Enabled = true;
}
}
private void btnStop_Click(object sender, EventArgs e)
{
Debug.WriteLine("You Cancelled");
_cs?.Cancel();
}
#JohnG's comment above basically sums up the issue you are probably having. You can't do a long-running set of computations on the User-Interface (UI) thread. You have to shuffle that off to another thread or task.
This is where parallel programming comes in: You want to click a button, have that initiate some work, but also allow for the UI to allow the user to click another button to stop that work.
So, here is a small example of what we are talking about:
private CancellationTokenSource _tokenSource;
private async void BtnStartWork_Click(object sender, EventArgs e)
{
BtnStartWork.Enabled = false;
_tokenSource = new CancellationTokenSource();
try
{
await Task.Run(() => CPUBoundWork(_tokenSource.Token));
}
finally
{
_tokenSource.Dispose();
_tokenSource = null;
BtnStartWork.Enabled = true;
}
}
private void BtnStopWork_Click(object sender, EventArgs e)
{
// signal cancel -- everyone listening will be notified.
_tokenSource?.Cancel();
}
private void CPUBoundWork(CancellationToken cancellationToken)
{
for (var i = 0; i < 10000; ++i)
{
if (cancellationToken.IsCancellationRequested)
{
// signaled for cancel, leave immediatly.
break;
}
// simulate CPU-bound work:
Thread.Sleep(10);
}
}
Some things may look new to you, for example: The BtnStartWork_Click method has the keyword async in it's signature. There is the keyword await being used. I won't explain everything going on here because there are plenty of resources out there about the subject.
But, this is the general idea of parallel programming. Your UI thread is still free to process messages dealing with the UI, and the framework makes sure to do the tasks you need it to do... all this with very little effort for the programmer.

Is it possible the WPF events to be added to the queue for execution in sequence?

If I add a button to the window and handle its click async event with a long process, it is possible to click multiple times a button even it is in progress. It keeps the events in the queue and executes them sequentially.
private async void Button_Click(object sender, RoutedEventArgs e)
{
await Task.Delay(2000);
Console.WriteLine("Button 1 ends");
}
The question: Is it possible to get like that result in sync form of event?
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10000000; i++)
{
}
Console.WriteLine("Button 1 ends");
}
We want to prevent multiple clicks when a user tries. If the system frozen or something went wrong, the user is able to click multiple times but we want to prevent this.
Edit: sorry I initially misunderstood the question thinking the requirement was to queue the clicks and prevent multiple simultaneous executions. Here is an update
private async void Button_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
button.IsEnabled = false;
try
{
// Perform long task here
}
finally
{
button.IsEnabled = true;
}
}
Why don't you use a flag inside the semaphore?
// Initialize the Semaphore to initial and max count of 1
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private bool alreadyExecuted = false;
private async void Button_Click(object sender, RoutedEventArgs e)
{
var tmp = await functionToCall();
}
private async Task<bool> functionToCall()
{
// Wait for semaphone to have an available slot
await _semaphore.WaitAsync();
try
{
if (!alreadyExecuted)
{
// Perform long task here
// Only one handler can be here at a time. You can increase this by increasing max count and initial count of the semaphore in the constructor.
}
}
finally
{
alreadyExecuted = true;
// Release the lock in a finally block to ensure it is released even in case of an error
_semaphore.Release();
}
return alreadyExecuted;
}

How can I make repeated calls to an async method with an on/off switch

In a WinForm application, I have an On/Off switch for a background process.
On Click, the program launch a process and restart it when it's finish, till you use the Off switch.
The following code is the working attempt that had multiple issues.
From Damien_The_Unbeliever comments:
suspend threads which means that they stay around forever, and implementing looping via recursion which could easily cause a stack overflow.
public partial class frmMain
{
Thread thread;
bool isRunning = false;
public frmMain()
{
InitializeComponent();
}
private void OnOffSwitch_Click(object sender, EventArgs e)
{
if (!isRunning)
{
btnSwitch.Text = "Stop";
isRunning = true;
thread = new Thread(doLoop);
thread.IsBackground = true;
thread.Start();
}
else
{
if (thread.IsAlive)
thread.Suspend();
btnSwitch.Text = "Start";
isRunning = false;
}
}
public void doLoop()
{
ClearScreenLogic.Run();
if (AutoReconnect)
ReconnectLogic.Run();
// Etc..
doLoop();
}
I am trying to switch from this working solution to background worker.
Implement your doLoop in the DoWork event of the BackGroundWorker and make sure you handle cancelation. Make sure to set the properties of your backgroundworker to WorkerReportprogress and WorkerSupportCancellation to true;
This is what you would need:
private void button1_Click(object sender, EventArgs e)
{
// on and off
if (backgroundWorker1.IsBusy)
{
// cancel if we have not already done so
if (!backgroundWorker1.CancellationPending)
{
backgroundWorker1.CancelAsync();
}
}
else
{
// start the background work
button1.BackColor = Color.Yellow;
backgroundWorker1.RunWorkerAsync();
}
}
// this runs on a background thread
// do not do stuff with the UI here
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int progress = 0;
// stop looping if cancellation is requested
while (!backgroundWorker1.CancellationPending)
{
// make it nice
backgroundWorker1.ReportProgress(progress);
ClearScreenLogic.Run();
if (AutoReconnect)
ReconnectLogic.Run();
// Etc..
progress++; // for feedback
}
}
// tell the use something is going on, this runs on the UI thread
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.ProgressPercentage.ToString();
}
// we're done, tell the user so
// this runs on the UI thread
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.BackColor = Color.Green;
label1.Text = "cancelled";
}
When implemented correctly your users will see something like this:

How can I display a loading control while a process is waiting for be finished?

I decided to use this third-party component to make a simple loading control in my windows form.
http://www.codeproject.com/Articles/14841/How-to-write-a-loading-circle-animation-in-NET
This works fine when turns on and off changing the property "Active" to true or false in a single request (one per time). The problem is when a process is waiting to be served, and I pretend to Active the loadingControl before the process starts and turn off when I "think" that the process has to be finished. When I do it, the image loading is shown as a static image. (Without animation).
I'm sorry for this question, I'm new in C#. But I think that I need to use Threads or something similar.
So my general code is this:
using [libraries here]...;
namespace [namespace here]
{
Public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.loadingCircle1.Visible = false;
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(showLoading));
this.loadingCircle1.Visible = true;
t.Start();
//Import an Excel
t.Abort();
}
public void showLoading()
{
loadingCircle1.Active = true;
loadingCircle1.RotationSpeed = 10;
}
}
}
But Always the Loading shows as a static image without the animation.
You create a thread, which simply sets two properties and then ends. The t.Abort will probably do nothing, since the thread will have been exited by that time. Even worse, you import the excel file on the UI thread, which blocks any animation and freezes the complete UI.
This is how you should make it:
Remark: Of course if your form is responsive, you must disable/enable the controls and prepare to the case what happens if your form is being closed during the load.
1. Using threads
If you really want to explicitly use threads, do it like this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Thread workerThread = null;
private void btnImport_Click(object sender, EventArgs e)
{
// start the animation (I used a progress bar, start your circle here)
progressBar1.Visible = true;
progressBar1.Style = ProgressBarStyle.Marquee;
// start the job and the timer, which polls the thread
btnImport.Enabled = false;
workerThread = new Thread(LoadExcel);
workerThread.Start();
timer1.Interval = 100;
timer1.Start();
}
private void LoadExcel()
{
// some work takes 5 sec
Thread.Sleep(5000);
}
private void timer1_Tick(object sender, EventArgs e)
{
if (workerThread == null)
{
timer1.Stop();
return;
}
// still works: exiting
if (workerThread.IsAlive)
return;
// finished
btnImport.Enabled = true;
timer1.Stop();
progressBar1.Visible = false;
workerThread = null;
}
}
2. Background worker
The BackgroundWorker can throw an event when it is finished:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += BackgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += BackgroundWorker1_RunWorkerCompleted;
}
private void btnImport_Click(object sender, EventArgs e)
{
// start the animation
progressBar1.Visible = true;
progressBar1.Style = ProgressBarStyle.Marquee;
// start the job
btnImport.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
LoadExcel();
}
private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
btnImport.Enabled = true;
progressBar1.Visible = false;
}
private void LoadExcel()
{
// some work takes 5 sec
Thread.Sleep(5000);
}
}
3. Using async-await
This is the simplest one.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void btnImport_Click(object sender, EventArgs e)
{
// start the waiting animation
progressBar1.Visible = true;
progressBar1.Style = ProgressBarStyle.Marquee;
// simply start and await the loading task
btnImport.Enabled = false;
await Task.Run(() => LoadExcel());
// re-enable things
btnImport.Enabled = true;
progressBar1.Visible = false;
}
private void LoadExcel()
{
// some work takes 5 sec
Thread.Sleep(5000);
}
}
I'd recommend to use async/await (for C# 5.0):
private void button1_Click(object sender, EventArgs e){
ImportAsync();
}
private async Task ImportAsync(){
// UI-thread
showLoading();
this.loadingCircle1.Visible = true;
// wait until task will be finished
await Task.Run(() => {
// different non-blocking thread for all the hard work, but without UI-stuff
// import an Excel
});
// going back to UI-thread
this.loadingCircle1.Visible = false;
}

How to stop a for loop using a button?

I have a loop that I would like to stop using a button.
Edited for better understanding:
I do realize that you cannot stop a button while a loop was running since it will not work as long as that current UI is running. What I'm really asking for is the most efficient way of creating a thread or using BGWorker to stop this. I have seen some methods, but most of them are for Java and not C#.
What I would like to do is:
private void start_Click(object sender, EventArgs e)
{
for(int i = 0; i < nums; i++)
{
doSomething();
}
}
private void stop_Click(object sender, EventArgs e)
{
stops start_Click()
}
You can't do that. For starters, the for loop is running synchronously on the UI thread, which means you won't even be able to click the "Stop" button.
Hence, you need to move the operations of the for loop onto another thread, which means you likely won't be using a for loop at all. You need to think about how the code inside actually needs to be executed, then based on how you are doing the processing, you can implement the "Stop" button.
A very simple way to do this would be to just:
new Thread(() =>
{
int i = 0;
while (!stop && i < num)
{
doSomething();
i++;
}
}).Start();
And set stop to stop the processing loop. In a more realistic scenario, you could queue up functions that you want to process, then stop dequeuing via a similar method. Unfortunately, its hard to reccommend a setup without knowing more details.
Any solution based on your code will also have the problem of the current doSomething() completing execution (which could take a while). Again, without more info, its hard to say what the best approach to fixing that is.
To keep your UI responsive to be able to cancel the running operation you can use a backgrounworker.
The backgroundworker does the work in an other thread while keeping your UI responsive:
private readonly BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
_backgroundWorker.DoWork += backgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
Disposed += Form1_Disposed;
}
private void Form1_Disposed(object sender, EventArgs e)
{
_backgroundWorker.Dispose();
}
private void StartLoop()
{
if ( !_backgroundWorker.IsBusy )
{
_backgroundWorker.RunWorkerAsync();
}
}
private void StopLoop()
{
_backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork( object sender , DoWorkEventArgs e )
{
var backgroundWorker = ( BackgroundWorker ) sender;
for ( var i = 0; i < 100; i++ )
{
if ( backgroundWorker.CancellationPending )
{
e.Cancel = true;
return;
}
// Do Work
}
}
private void backgroundWorker_RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e )
{
if ( e.Cancelled )
{
// handle cancellation
}
if ( e.Error != null )
{
// handle error
}
// completed without cancellation or exception
}
IMHO, it's likely the best approach here is to convert your work to an asynchronous operation and then use the async/await idiom for the loop. E.g.:
private bool _stopLoop;
private async void start_Click(object sender, EventArgs e)
{
_stopLoop = false;
for(int i = 0; i < nums && !_stopLoop; i++)
{
await Task.Run(() => doSomething());
}
}
private void stop_Click(object sender, EventArgs e)
{
_stopLoop = true;
}
This allows the loop itself to execute in the UI thread where the _stopLoop variable is being managed, but without actually blocking the UI thread (which among other things would prevent the "Stop" button from being clicked).
Unfortunately, you didn't provide details about how doSomething() works. It's possible there's a good way to convert that whole method to be an async method, but I can't comment on that without the actual code.
Note that this approach will only interrupt the loop at a point in between each operation. If you want to be able to interrupt the doSomthing() operation itself, you'll have to provide a mechanism for that. One likely approach would be to use CancellationSource and CancellationToken, which provides a convenient way to express cancellation semantics.
Try using an async/await approach. It's quite easy!
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
}
private CancellationTokenSource _tokenSource;
private async void start_Click(object sender, EventArgs e)
{
if (_tokenSource != null)
return;
_tokenSource = new CancellationTokenSource();
var ct = _tokenSource.Token;
await Task.Factory.StartNew(() =>
{
for (; ; )
{
if (ct.IsCancellationRequested)
break;
doSomething();
}
}, ct);
_tokenSource = null;
}
private int _labelCounter;
private void doSomething()
{
// do something
Invoke((Action)(() =>
{
myLabel.Text = (++_labelCounter).ToString();
}));
}
private void stop_Click(object sender, EventArgs e)
{
if (_tokenSource == null)
return;
_tokenSource.Cancel();
}
}
try this :
bool stop=false;
private void start_Click(object sender, EventArgs e)
{
for(int i = 0; i < nums&& !bool; i++)
{
doSomething();
}
}
and in the click event
set
stop=true;

Categories