启用在功能正在进行时单击按钮。

时间:2013-07-09 03:12:35

标签: c# multithreading winforms thread-safety

我有两个按钮启动和停止。在开始按钮的单击时,我想打一个表并循环记录,显示记录在循环中的时间差(所用时间)。

然而,当单击开始按钮时,由于记录数量很高,我无法单击“停止”按钮,我试图单击开始按钮,独立运行该过程,例如,如果处理了5000条记录,当我点击停止按钮时,我想显示这些5000条记录的时间,再次点击开始时,继续从中断的记录。

我如何独立处理两个事件?任何想法都会非常有用。谢谢。

private void startQueries()
{


thread=

new Thread(this.LoadResults);

thread.IsBackground =true;

thread.Start();

}

private void LoadResults()
{
 //Method that runs a select query - fetches 1000s of records
 Timespan startTime = <<Record the start time>>;

 //Loop thro' all the 1000s of records;

Timespan stopTime=<<Record the stop time>>;

//Display the difference between start and stop time.

}

    private void btnStart_Click(object sender, EventArgs e)
    {

    if(thread!=null)
    {
    if (thread.ThreadState == ThreadState.Running)
    {
    //do nothing
    }
    else if (thread.ThreadState == ThreadState.Suspended)
    {
    startQueries();
    }

    }

private void btnStop_Click(object sender, EventArgs e)
{
  //Stop the running thread

 // Need to display the time between the thread start and stop.
}
}

2 个答案:

答案 0 :(得分:1)

您可以使用BackgroundWorker的好处。

您可以直接跳到代码,但只是为了获取一般信息,BackgroundWorker以友好的方式包装了后台线程的管理。 您需要使用的主要事件是:

  1. DoWork - 在另一个线程上触发(即异步),这适用于繁重而缓慢的工作。它是通过调用“RunWorkerAsync”方法触发的。
  2. ProgressChanged - 在调用“RunWorkerAsync”的线程上触发它,通常是您的GUI线程。它通过调用“workerInstance.ReportProgress()”来触发,通常来自您的实际工作职能。
  3. RunWorkerCompleted - 在您调用“RunWorkerAsync”的线程上触发,通常是您的GUI线程。它在DoWork事件处理程序返回时触发。这是在作业完成后所需的操作。

  4. 我认为你有两种选择:

    1. 如果您可以过早退出LoadResults功能:

      /// <summary>
      /// The BackgroundWorker to handle you async work
      /// </summary>
      BackgroundWorker bw = new BackgroundWorker
      {
          WorkerReportsProgress = true,
          WorkerSupportsCancellation = true
      };
      
      /// <summary>
      /// Handles the Click event of the btnStart control.
      /// </summary>
      /// <param name="sender">The source of the event.</param>
      /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
      private void btnStart_Click(object sender, EventArgs e)
      {
          if (bw.IsBusy)
          {
              return;
          }
      
          System.Diagnostics.Stopwatch sWatch = new System.Diagnostics.Stopwatch();
          bw.DoWork += (bwSender, bwArg) =>
          {
              //what happens here must not touch the form
              //as it's in a different thread        
              sWatch.Start();
              this.LoadResults();
          };
      
          bw.ProgressChanged += (bwSender, bwArg) =>
          {
              //update progress bars here
          };
      
          bw.RunWorkerCompleted += (bwSender, bwArg) =>
          {
              //now you're back in the UI thread you can update the form
              //remember to dispose of bw now               
      
              sWatch.Stop();
              MessageBox.Show(String.Format("Thread ran for {0} milliseconds",sWatch.ElapsedMilliseconds));
              //work is done, no need for the stop button now...
              this.btnStop.Enabled = false;
              bw.Dispose();
          };
      
          //lets allow the user to click stop
          this.btnStop.Enabled = true;
          //Starts the actual work - triggerrs the "DoWork" event
          bw.RunWorkerAsync();
      
      
      }
      
      /// <summary>
      /// Handles the Click event of the btnStop control.
      /// </summary>
      /// <param name="sender">The source of the event.</param>
      /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
      private void btnStop_Click(object sender, EventArgs e)
      {
          //Stop the running thread
          this.bw.CancelAsync();
          // Need to display the time between the thread start and stop.
      }
      
      private void LoadResults()
      {
          //When you check if cancelation is pending:
          for (int i = 0; i < 5; i++)
          {
              //Simulating some job time
              Thread.Sleep(1000);
      
              if (bw.CancellationPending)
              {
                  return;
              }
      
          }
      
      
      }
      
    2. 如果无法过早退出LoadResults函数: (基于Robs answer

          /// <summary>
          /// The BackgroundWorker to handle you async work
          /// </summary>
          BackgroundWorker bw = new BackgroundWorker
          {
              WorkerReportsProgress = true,
              WorkerSupportsCancellation = true
          };
      
          /// <summary>
          /// Handles the Click event of the btnStart control.
          /// </summary>
          /// <param name="sender">The source of the event.</param>
          /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
          private void btnStart_Click(object sender, EventArgs e)
          {
              if (bw.IsBusy)
              {
                  return;
              }
      
              System.Diagnostics.Stopwatch sWatch = new System.Diagnostics.Stopwatch();
              bw.DoWork += (bwSender, bwArg) =>
              {
                  //what happens here must not touch the form
                  //as it's in a different thread        
                  sWatch.Start();
      
      
                  var _child = new Thread(() =>
                  {
                      this.LoadResults();
      
                  });
                  _child.Start();
                  while (_child.IsAlive)
                  {
                      if (bw.CancellationPending)
                      {
                          _child.Abort();
                          bwArg.Cancel = true;
                      }
                      Thread.SpinWait(1);
                  }                
      
              };
      
              bw.ProgressChanged += (bwSender, bwArg) =>
              {
                  //update progress bars here
              };
      
              bw.RunWorkerCompleted += (bwSender, bwArg) =>
              {
                  //now you're back in the UI thread you can update the form
                  //remember to dispose of bw now               
      
                  sWatch.Stop();
                  MessageBox.Show(String.Format("Thread ran for {0} milliseconds",sWatch.ElapsedMilliseconds));
                  //work is done, no need for the stop button now...
                  this.btnStop.Enabled = false;
                  bw.Dispose();
              };
      
              //lets allow the user to click stop
              this.btnStop.Enabled = true;
              //Starts the actual work - triggerrs the "DoWork" event
              bw.RunWorkerAsync();
      
      
          }
      
          /// <summary>
          /// Handles the Click event of the btnStop control.
          /// </summary>
          /// <param name="sender">The source of the event.</param>
          /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
          private void btnStop_Click(object sender, EventArgs e)
          {
              //Stop the running thread
              this.bw.CancelAsync();
              // Need to display the time between the thread start and stop.
          }
      
          private void LoadResults()
          {
              //Simulation job time...
              Thread.Sleep(5000);
          } 
      

答案 1 :(得分:0)

如果您使用的是.NET 4.5或更高版本,则可以使用较新的异步功能,即使它仍然是异步发生的,也会为您的代码提供干净,顺序的流程。

public partial class Form1 : Form
{
    CancellationTokenSource _cancellationSource;
    int _currentRecord;
    int _maxRecord;

    public Form1()
    {
        InitializeComponent();
        _currentRecord = 0;
        _maxRecord = 5000;
    }

    private async void btnStart_Click(object sender, EventArgs e)
    {
        await StartQueriesAsync();
    }

    private async Task StartQueriesAsync()
    {
        _cancellationSource = new CancellationTokenSource();
        var sw = new Stopwatch();

        try
        {
            // for Progress<>, include the code that outputs progress to your UI
            var progress = new Progress<int>(x => lblResults.Text = x.ToString());
            sw.Start();
            // kick off an async task to process your records
            await Task.Run(() => LoadResults(_cancellationSource.Token, progress));
        }
        catch (OperationCanceledException)
        {
            // stop button was clicked
        }

        sw.Stop();
        lblResults.Text = string.Format(
            "Elapsed milliseconds: {0}", sw.ElapsedMilliseconds);
    }

    private void LoadResults(CancellationToken ct, IProgress<int> progress)
    {
        while(_currentRecord < _maxRecord)
        {
            // watch for the Stop button getting pressed
            if (ct.IsCancellationRequested)
            {
                ct.ThrowIfCancellationRequested();
            }

            // optionally call this to display current progress
            progress.Report(_currentRecord);

            // simulate the work here
            Thread.Sleep(500);

            _currentRecord++;
        }
    }

    private void btnStop_Click(object sender, EventArgs e)
    {
        _cancellationSource.Cancel();
    }
}

当您单击“开始”按钮时,任务将通过Task.Run启动并传递CancellationToken以观察“停止”按钮以及包含要更新的代码的Progress对象随着长期运行的任务的运行,你的用户界面。

在任务之前还会创建一个Stopwatch对象来记录任务运行的时间。

另请注意,当前记录会保留,以便在您停止后恢复时,它会从中断处继续。