1

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

tiptop
  • 35
  • 3
  • 1
    Perhaps this answer will help to show how to use `async` and `await`: https://stackoverflow.com/a/19985988/3938951? – NiMux Jun 25 '22 at 13:36
  • 1
    Can you say which `Timer` class you're using? Is this Winforms? Is `timer1` a `System.Windows.Forms.Timer`? (It can make a difference!) We were just talking about this very thing. Check out the code example of an `async` timer handler in my answer [here](https://stackoverflow.com/a/72708547/5438626) it may be helpful for what you're trying to do. – IVSoftware Jun 25 '22 at 13:58
  • Ok I will look at your two suggestions and to answer IVSoftware I think timer1 must be a System.Windows.Forms.Timer because I took it from the Toolbox – tiptop Jun 25 '22 at 14:04
  • 1
    Sounds right! Plz consider editing your post and adding `Winforms` as a tag. – IVSoftware Jun 25 '22 at 14:07
  • I notice in your code that you're checking `if (timer1.Enabled)` in the timer handler but it seems unlikely that the timer tick handler would be executing in the first place if the timer _weren't_ enabled. Does that `else` block ever get executed? – IVSoftware Jun 25 '22 at 14:15
  • I don't think I understood correctly but when the startRPLoop button is clicked the loop starts correctly and when the stopRPLoop button is clicked the loop stops correctly. The only problem is that due to Thrad.Sleep my program freezes for 1000ms so I would rather use await Task.Delay(1000); but I don't know how to go about it. Here is the link to my code: https://github.com/tiptoppp/Cheatcord/blob/main/Cheatcord/Tabs/UCrploop.cs I hope it can help you to understand me – tiptop Jun 25 '22 at 14:28
  • Yes, I believe I _do_ understand what you want. Using a `Timer` with the `async` delays makes everything more complicated. Would it be OK if I showed you some code that does what you want but _doesn't_ use `Timer`? It's way simpler. Or I can show you the other way. Your choice. – IVSoftware Jun 25 '22 at 16:31
  • Yes of course, I have no problem with you offering me a different and simpler solution than the one I am considering. – tiptop Jun 25 '22 at 16:50

1 Answers1

1

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

IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • [Clone](https://github.com/IVSoftware/await-task-delay-in-loop.git) this sample. – IVSoftware Jun 25 '22 at 16:54
  • Thank you very much for the help you have given me and it will be very useful! But on the other hand I need two buttons, a Start button, a Stop button and not a checkBox, in your opinion is this a simple modification to make? – tiptop Jun 25 '22 at 19:24
  • 1
    Ordinarily I would bow out after answering the original question, but I did take certain liberties with your posted code so this one's free. Besides, I feel it's instructional to point out that the UI state must always _disallow_ actions that would be **bad** mkay? In this case, disable [Start] if you're running and disable [Stop] if you're not. I put the **Edit** below the original answer. – IVSoftware Jun 25 '22 at 20:11
  • You not only helped me but you actually wrote the entire code for me so I really want to thank you for the time you gave me! And thank you also for the indications provided with the code, it allowed me to understand how it works :) – tiptop Jun 25 '22 at 20:32
  • Ugh! "Writing your code" is a bug not a feature LOL. Thank you for letting me know that my intention, _to increase your understanding of `async`_, met with some success. Don't get me kicked off the site, though. SO is definitely **not** a code writing service. That said, I'm very glad that this has increased your understanding. – IVSoftware Jun 25 '22 at 20:37
  • 1
    Sorry for the terms used, I meant that without your bug solving, I would surely not have found a solution to my problem – tiptop Jun 25 '22 at 20:49