【问题标题】:How to stop a for loop using a button?如何使用按钮停止 for 循环?
【发布时间】:2015-02-20 21:17:09
【问题描述】:

我有一个循环,我想停止使用按钮。 编辑以便更好地理解:

我确实意识到,您不能在循环运行时停止按钮,因为只要当前 UI 正在运行,它就不会工作。我真正要求的是创建线程或使用 BGWorker 来阻止它的最有效方法。我见过一些方法,但大多数是用于 Java 而不是 C#。

我想做的是:

private void start_Click(object sender, EventArgs e)
{

    for(int i = 0; i < nums; i++)
    {
        doSomething();
    }
}

private void stop_Click(object sender, EventArgs e)
{

    stops start_Click()
}

【问题讨论】:

  • 除非您将循环放在另一个线程中,否则您将无法执行您所要求的操作。您的循环阻止了与您的按钮的交互。
  • 我知道人们不会喜欢这个,但可以通过在循环中输入 Application.DoEvents() 并检查布尔变量来尝试:)
  • 您使用的是 WinForms 还是 WPF 或其他什么?

标签: c# loops button for-loop


【解决方案1】:

你不能那样做。对于初学者,for 循环在 UI 线程上同步运行,这意味着您甚至无法能够单击“停止”按钮。

因此,您需要将 for 循环的操作移至另一个线程,这意味着您可能根本不会使用 for 循环。您需要考虑实际上里面的代码需要如何执行,然后根据您的处理方式,您可以实现“停止”按钮。

一个非常简单的方法是:

new Thread(() =>
{
   int i = 0;
   while (!stop && i < num)
   {
      doSomething();
      i++;
   }
 }).Start();

并设置stop 以停止处理循环。在更现实的情况下,您可以将要处理的函数排队,然后通过类似的方法停止出队。不幸的是,如果不了解更多细节,很难推荐设置。

任何基于您的代码的解决方案也会存在当前doSomething() 完成执行的问题(这可能需要一段时间)。同样,如果没有更多信息,很难说解决这个问题的最佳方法是什么。

【讨论】:

    【解决方案2】:

    为了让您的 UI 响应能够取消正在运行的操作,您可以使用后台工作程序。 backgroundworker 在另一个线程中完成工作,同时保持你的 UI 响应:

        private readonly BackgroundWorker _backgroundWorker;
    
        public Form1()
        {
            InitializeComponent();
    
            _backgroundWorker = new BackgroundWorker
            {
                WorkerSupportsCancellation = true
            };
            _backgroundWorker.DoWork += backgroundWorker_DoWork;
            _backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
    
            Disposed += Form1_Disposed;
        }
    
        private void Form1_Disposed(object sender, EventArgs e)
        {
            _backgroundWorker.Dispose();
        }
    
        private void StartLoop()
        {
            if ( !_backgroundWorker.IsBusy )
            {
                _backgroundWorker.RunWorkerAsync();
            }
        }
    
        private void StopLoop()
        {
            _backgroundWorker.CancelAsync();
        }
    
        private void backgroundWorker_DoWork( object sender , DoWorkEventArgs e )
        {
            var backgroundWorker = ( BackgroundWorker ) sender;
    
            for ( var i = 0; i < 100; i++ )
            {
                if ( backgroundWorker.CancellationPending )
                {
                    e.Cancel = true;
                    return;
                }
                // Do Work
            }
        }
    
        private void backgroundWorker_RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e )
        {
            if ( e.Cancelled )
            {
                // handle cancellation
            }
            if ( e.Error != null )
            {
                // handle error
            }
    
            // completed without cancellation or exception
        }
    

    【讨论】:

    【解决方案3】:

    恕我直言,这里最好的方法可能是将您的工作转换为异步操作,然后使用 async/await 成语进行循环。例如:

    private bool _stopLoop;
    
    private async void start_Click(object sender, EventArgs e)
    {
        _stopLoop = false;
    
        for(int i = 0; i < nums && !_stopLoop; i++)
        {
            await Task.Run(() => doSomething());
        }
    }
    
    private void stop_Click(object sender, EventArgs e)
    {
        _stopLoop = true;
    }
    

    这允许循环本身在管理 _stopLoop 变量的 UI 线程中执行,但不会实际阻塞 UI 线程(这会阻止单击“停止”按钮)。

    很遗憾,您没有提供有关 doSomething() 工作原理的详细信息。可能有一种好方法可以将整个方法转换为 async 方法,但如果没有实际代码,我无法对此发表评论。

    请注意,这种方法只会在每个操作之间的某个点中断循环。如果您希望能够中断doSomthing() 操作本身,则必须为此提供一种机制。一种可能的方法是使用CancellationSourceCancellationToken,这提供了一种表达取消语义的便捷方式。

    【讨论】:

      【解决方案4】:

      尝试使用异步/等待方法。这很容易!

      public partial class MyForm : Form
      {
          public MyForm()
          {
              InitializeComponent();
          }
      
          private CancellationTokenSource _tokenSource;
      
          private async void start_Click(object sender, EventArgs e)
          {
              if (_tokenSource != null)
                  return;
      
              _tokenSource = new CancellationTokenSource();
              var ct = _tokenSource.Token;
      
              await Task.Factory.StartNew(() =>
              {
                  for (; ; )
                  {
                      if (ct.IsCancellationRequested)
                          break;
                      doSomething();
                  }
              }, ct);
      
              _tokenSource = null;
          }
      
          private int _labelCounter;
      
          private void doSomething()
          {
              // do something
              Invoke((Action)(() =>
              {
                  myLabel.Text = (++_labelCounter).ToString();
              }));
          }
      
          private void stop_Click(object sender, EventArgs e)
          {
              if (_tokenSource == null)
                  return;
      
              _tokenSource.Cancel();
          }
      }
      

      【讨论】:

      • 这个问题是我目前使用的是Visual Studio 2010,不能使用async/await。下载新版本值得吗,还是我无法使用 2013 运行以前的程序?
      • 没问题,在这种情况下,MS 试图保持完全的向后兼容性。您的项目将转换为 VS 2013 项目,然后您应该在项目属性中选择相应的目标框架(例如 4.5.1)。就这样。 async/await 是对 async ui 编程的极大简化,值得。
      【解决方案5】:

      试试这个:

      bool stop=false;
      
      private void start_Click(object sender, EventArgs e)
      {
      for(int i = 0; i < nums&& !bool; i++)
      {
      doSomething();
      }
      }
      

      在点击事件中

      设置

      stop=true;
      

      【讨论】:

      • 不行,UI线程被阻塞了。如果在 separate 线程上执行,这可以工作,但这可能会导致其他问题...
      • 这行不通,因为 UI 已经在运行当前任务,并且在完成之前不会运行单独的任务。
      猜你喜欢
      • 2017-01-26
      • 2013-08-14
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      • 2012-07-14
      • 1970-01-01
      • 2016-03-12
      • 1970-01-01
      相关资源
      最近更新 更多