Вырыв из цикла с нажатием кнопки - С#

У меня есть вопрос, касающийся зацикливания с событием нажатия кнопки, я перепробовал много методов и искал много страниц в поисках простого ответа за прошедший час, но правда в том, что каждый ответ просто выглядит как инопланетный код, вероятно, потому что я все еще очень новый для разработки.

Вот упрощенная версия того, что я пытаюсь сделать:

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

Очевидно, что это не мой API, или изобретать было бы бесполезно, однако сам код длинный, и вы, ребята, всегда спрашиваете "соответствующий код" (без неуважения), так что это так.

Моя проблема: выход из цикла спама при нажатии кнопки 2, код для меня выглядит достаточно прилично для API, чтобы понять, но каждый раз, когда нажимается кнопка 1, API зависает.

Ответ 1

Используйте фонового работника, чтобы делать свою работу. Вы можете использовать функцию отмены, чтобы выйти из нее, когда вы закончите. Ваш цикл, если он у вас есть, будет блокировать поток пользовательского интерфейса при синхронном выполнении, поэтому ваш графический интерфейс перестает отвечать на запросы. Обратите внимание, что если вы выполняете какое-либо взаимодействие с пользовательским интерфейсом в делегате do work, вам необходимо вернуться обратно в поток пользовательского интерфейса (например, с помощью invoke).

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

Обновление 2019: BackgroundWorker настоящее время в значительной степени устарел и заменен функцией async/await в более поздних версиях С#, которая проще в использовании. Вот пример того, как добиться того же, используя эту функцию:

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

Ответ 2

Есть одна важная вещь, которую нужно помнить:

Пока ваш код выполняется, пользователь не может взаимодействовать с вашим пользовательским интерфейсом.

Это означает: вам сначала нужно выйти из цикла (т.е. вернуться из метода Spam), а затем пользователь может нажать кнопку Button2.

Это твердая правда, потому что это означает, что вы не можете написать код так, как вы хотели. К счастью, есть несколько способов обойти это:

  • Не используйте цикл. Используйте какой-то таймер, чтобы выполнить "спам". Button1 запускает таймер, Button2 останавливает его. Какой тип таймера доступен, зависит от используемой вами библиотеки пользовательского интерфейса (у WinForms есть Timer, WPF имеет DispatcherTimer).

  • Сделайте "спам" в фоновом потоке. Это позволит вашему пользовательскому интерфейсу оставаться отзывчивым, и вы можете общаться с фоновым потоком, например, установив volatile Boolean. Это, однако, расширенная тема (и может быстро привести к сложным проблемам синхронизации), поэтому я предлагаю вам сначала попробовать другой вариант.

Ответ 3

Взгляните на эту концепцию:

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

Однако пользователь не сможет нажать кнопку, если MessageBox продолжает выскакивать, поскольку он занимает основной поток пользовательского интерфейса. Чтобы предотвратить это, вы можете использовать BackgroundWorker или запустить новый поток.

Ответ 4

При нажатии кнопки1 вызывается метод спама и начинается цикл. Когда вы нажимаете кнопку2, вызывается метод спама, но это не то же самое. Это второе выполнение, поэтому оно проверит условие и не войдет в цикл, но цикл на первом подоконнике будет запущен.

Вы должны использовать флаг, и цикл должен использовать этот флаг, чтобы определить, должен ли он работать. Он должен выглядеть примерно так:

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