Home > Enterprise >  How to pause inside a loop without using Thread.Sleep?
How to pause inside a loop without using Thread.Sleep?

Time:06-26

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 :)

CodePudding user response:

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.

Form with event tracer

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

two buttons version

  • Related