Breaking from a loop with button click - C#

2020-02-08 04:20发布

I have a question regarding looping with button click event, I've tried many methods & searched many pages in search for a simple answer for the past hour, but the truth is each answer just looks like alien code, probably because I'm still very new to developing.

Here's a simplified version of what I'm trying to do :

private string Message = "Hello";

private void Spam(bool loop)
{
    if (loop == true)
    {
        while (loop == true)
        {
            MessageBox.Show(Message);
        }
    }
    else { MessageBox.Show("Spamming has stopped !! "); }
}

private void button1_Click(object sender, EventArgs e)
{
    Spam(true);
}
private void button2_Click(object sender, EventArgs e)
{
    Spam(false);
}

Obviously this isn't my API, or it'd be a useless thing to invent, however, the code itself is long & you guys always ask for "relevant code" (No disrespect), so there it is.

My problem : Breaking out of the spam loop upon clicking button 2, the code to me looks decent enough for the API to figure out, but each time button 1 is clicked, the API freezes.

标签: c# loops button
4条回答
劳资没心,怎么记你
2楼-- · 2020-02-08 05:06

There's one important thing to remember:

While your code is being executed, the user cannot interact with your user interface.

That means: You first need to exit the loop (i.e. return from the Spam method), and then the user can click Button2.

That's a hard truth, because it means you cannot write the code in the way you wanted to. Fortunately, there are a few ways to work around that:

  • Don't use a loop. Use some kind of timer to do the "spamming". Button1 starts the timer, Button2 stops it. What kind of timer is available depends on the user interface library you use (WinForms has a Timer, WPF has a DispatcherTimer).

  • Do the "spamming" in a background thread. This will allow your user interface to stay responsive, and you can communicate with the background thread, for example, by setting a volatile Boolean. This, however, is an advanced topic (and can quickly lead to complex synchronization issues), so I suggest that you try the other option first.

查看更多
唯我独甜
3楼-- · 2020-02-08 05:10

When you click button1 the Spam method is called and loop is starting. When you click button2 Spam method is called but it's not the same. It's the second execution, so it will check the condition and won't enter into the loop, but the loop in the first call sill will be running.

You should use a flag and the loop should use that flag to determine whether it should be still running. It should look something like that:

bool run = false;

string message = "This API is not original";

private void Spam()
  {
       while (run == true)
      {
        MessageBox.Show(message); 
      } 
     } 
   }

 private void button1_Click(object sender, EventArgs e)
        {
          message = "Hellooo";
          flag = true;
          Spam();
        }
 private void button2_Click(object sender, EventArgs e)
        {
          flag = false;
        }
查看更多
男人必须洒脱
4楼-- · 2020-02-08 05:11

Take a look at this concept:

private bool loop = false;

private void Start()
{
    loop = true;
    Spam("Some Message??");
}

private void Spam(string message)
{
    while (loop)
    {
        MessageBox.Show("This API is not original"); 
    }
}

private void button1_Click(object sender, EventArgs e)
{
    loop = true;
}

private void button2_Click(object sender, EventArgs e)
{
    loop = false;
}

However, the user won't be able to press a button if a MessageBox keeps popping up as it takes up the main UI thread. In order to prevent this you could use BackgroundWorker or start a new thread.

查看更多
Lonely孤独者°
5楼-- · 2020-02-08 05:15

Use a background worker to do your work. You can use the cancellation feature to break out of it when you're done. Your loop as you have it will block the UI thread when executed syncronously, which is why your GUI becomes unresponsive. Note if you do any interaction with the UI in the do work delegate, you need to marshal back onto the UI thread (via invoke for example).

private BackgroundWorker _worker = null;

private void goButton_Click(object sender, EventArgs e)
{
    _worker = new BackgroundWorker();
    _worker.WorkerSupportsCancellation = true;

    _worker.DoWork += new DoWorkEventHandler((state, args) =>
    {
        do
        {
            if (_worker.CancellationPending)                
                break;

            Console.WriteLine("Hello, world");

        } while (true);
    });

    _worker.RunWorkerAsync();
    goButton.Enabled = false;
    stopButton.Enabled = true;
}

private void stopButton_Click(object sender, EventArgs e)
{
    stopButton.Enabled = false;
    goButton.Enabled = true;
    _worker.CancelAsync();
}

Update 2019: BackgroundWorker is now largely obsolete, replaced by the async/await feature in later versions of C# which is easier to use. Here is an example of how to achieve the same thing using that feature:

private CancellationTokenSource _canceller;

private async void goButton_Click(object sender, EventArgs e)
{
    goButton.Enabled = false;
    stopButton.Enabled = true;

    _canceller = new CancellationTokenSource();
    await Task.Run(() =>
    {
        do
        {
            Console.WriteLine("Hello, world");
            if (_canceller.Token.IsCancellationRequested)
                break;

        } while (true);
    });

    _canceller.Dispose();
    goButton.Enabled = true;
    stopButton.Enabled = false;
}

private void stopButton_Click(object sender, EventArgs e)
{
    _canceller.Cancel();
}
查看更多
登录 后发表回答