点击按钮跳出循环 - C#

14

我有一个关于按钮点击事件循环的问题,我尝试了许多方法并在过去一个小时里搜索了许多页面,寻找一个简单的答案,但是事实上每个答案看起来都像外星人代码,可能是因为我还是一个新手。

这是我正在尝试做的事情的简化版本:

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都会冻结。


单线程无法实现。如果至少使用两个线程,就可以完成 - 一个执行后台任务(“循环”),另一个处理用户点击事件,并且这两个线程之间共享资源(例如,C#中的static bool)。 - Lanorkin
你不需要在第二个按钮中调用 Spam(Hi,false),只需定义一个名为 BreakLoop 的全局变量,默认值为 false。当你点击第一个按钮时,将其值更改为 false,在 while 循环中检查此变量是否仍为 true,如果是,则继续执行。当你点击第二个按钮时,将此变量设置为 true,这样就可以跳出循环了。 - Monah
你们是在装作不会说外星语吗?还是故意这样做的?-_-x - Hellooo123
一个BackgroundWorker似乎更适合这个任务。您当前正在阻塞UI线程,因此GUI无响应。 - John
4个回答

17

使用后台工作者来执行任务。当任务完成时,您可以使用取消功能退出任务。如您所写的循环同步执行会阻塞 UI 线程,这就是为什么 GUI 变得无响应的原因。请注意,如果在“执行工作”委托中与 UI 交互,则需要通过调用(例如 invoke)返回到 UI 线程。

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现在已经被大量弃用,由C#的后续版本中更容易使用的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
优秀的答案,是对后台工作者非常好的介绍。你让我今天过得很愉快,干得好。 - Hellooo123
有人知道如何将匿名对象传递给 RunWorkerAsync() 吗?就像 backgroundWorker1.RunWorkerAsync(new { start = 108, end = 502404 }); 这样。 - boctulus
1
@Boctulus,就像你所说的那样应该没问题。在“DoWork”方法中,它应该出现在“DoWorkEventArgs”参数中(如上面的示例中的args)。然后,您可以像这样获取引用后使用对象:dynamic anon = args.Argument; - steve16351
非常感谢 @steve16351 ... 你救了我 :) - boctulus
这个最新的更新似乎只能在 .net 4.5+ 中使用。 - vr_driver

7

有一件重要的事情需要记住:

当您的代码正在执行时,用户无法与您的用户界面进行交互。

这意味着:您首先需要退出循环(即从Spam方法返回),然后用户才能单击Button2。

这是一个艰难的真相,因为它意味着您不能按照所需的方式编写代码。幸运的是,有几种方法可以解决这个问题:

  • 不使用循环。使用某种计时器来进行“垃圾邮件”操作。Button1启动计时器,Button2停止计时器。可用的计时器类型取决于您使用的用户界面库(WinForms具有Timer,WPF具有DispatcherTimer)。

  • 后台线程中执行“垃圾邮件”操作。这将使您的用户界面保持响应,并且您可以通过设置volatile Boolean来与后台线程通信。但是,这是一个高级主题(并且可能很快导致复杂的同步问题),因此我建议您首先尝试其他选项。


我最近开始使用的几个API中都看到了后台线程的使用,所以这绝对是我应该集中注意力的东西,谢谢你提醒,非常有用。 - Hellooo123

0

看看这个概念:

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不断弹出并占用主UI线程,用户将无法按下按钮。为了防止这种情况发生,您可以使用BackgroundWorker或启动一个新的线程。

将MessageBox更改为label1.Text,导致程序冻结,因为while循环始终为真,没有机会触发按钮2的点击事件。 - Hellooo123

0

当您单击button1时,将调用Spam方法并启动循环。当您单击button2时,将调用Spam方法,但它不同。这是第二次执行,因此它将检查条件并不会进入循环,但第一次调用中的循环仍将运行。

您应该使用标志,循环应该使用该标志来确定它是否仍在运行。它应该看起来像这样:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接