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();
}
}
