【问题标题】:How to stop the implementation during the process with backgroundWorker如何在使用 backgroundWorker 的过程中停止执行
【发布时间】:2016-09-06 02:30:39
【问题描述】:

因此,此代码采用随机起始数字的递增数字流的值。如果 start1 值大于 start2 我想用文本框显示相应的行,否则是另一行。

问题是我无法停止程序,直到给定的周期数不满足。按钮只是在植入过程中挂起。我知道发生这种情况的原因是一个循环。现在我试图用 backgroundWorker 停止它,但我得到了相同的结果,取消按钮以同样的方式挂起。

我想与位于 backgroundWorker1_ProgressChanged 内的 backgroundWorker 一起使用的代码。我不太明白这里发生了什么。也许你帮我弄清楚我做错了什么:

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace XX_8_0
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.WorkerSupportsCancellation = true;
        }

        private void startAsyncButton_Click(object sender, EventArgs e)
        {    
            if (backgroundWorker1.IsBusy != true)
            {
                backgroundWorker1.RunWorkerAsync();
            }
        }

        private void cancelAsyncButton_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.WorkerSupportsCancellation == true)
            {
                backgroundWorker1.CancelAsync();
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            for (int i = 1; i <= 10; i++)
            {
                if (worker.CancellationPending == true)
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    System.Threading.Thread.Sleep(500);
                    worker.ReportProgress(i * 10);
                }
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        { 
            var random = new Random();
            var start1 = random.Next(0, 100);
            var start2 = random.Next(0, 100);
            var incrementor1 = start1 > 50 ? -1 : 1;
            var incrementor2 = start2 > 50 ? -1 : 1;
            var cV1 = start1;
            var cV2 = start2;

            for (var i = 0; i < 1000; i++)
            {
                if (cV1 == 101) incrementor1 = -1;
                if (cV1 == 0) incrementor1 = 1;

                if (cV2 == 101) incrementor2 = -1;
                if (cV2 == 0) incrementor2 = 1;

                if (cV1 > cV2)
                {
                    textBox1.AppendText("ID: (" + i + ") CV1: (1): [" + cV1 + "] CV2: (0) [" + cV2 + "]\n");
                }
                else
                {
                    textBox1.AppendText("ID: (" + i + ") CV1: (0): [" + cV1 + "] CV2: (1) [" + cV2 + "]\n");
                }

                cV1 += incrementor1;
                cV2 += incrementor2;
            }
        }
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled == true)
            {
                resultLabel.Text = "Canceled!";
            }
            else if (e.Error != null)
            {
                resultLabel.Text = "Error: " + e.Error.Message;
            }
            else
            {
                resultLabel.Text = "Done!";
            }
        }    
    }
}

【问题讨论】:

  • 除了我的回答之外,您还可以查看该页面上的相关部分以获取有用信息。显然,上面的实现非常糟糕,并且对BackgroundWorker 的工作原理缺乏了解。

标签: c# loops backgroundworker cycle


【解决方案1】:

注意:我认为您的示例可能不是 BackgroundWorker 可以做的一个很好的示例。从本质上讲,您的代码只是稍作休息,然后报告进度,在另一个线程中几乎没有用处。


您的backgroundWorker1_ProgressChanged 很重,要添加 1000 个textBox1.AppendText(),因此可能需要一些时间。

按钮只是在植入过程中挂起。我知道发生这种情况的原因是一个循环。

当您考虑 backgroundWorker1_ProgressChanged 在 UI 线程上执行时,无论您在 cancel 上单击多少次,都不会在 backgroundWorker1_ProgressChanged 返回之前得到处理。所有 UI 处理都必须由 UI 线程完成。需要指出的是,后台工作线程在此期间也被挂起,不幸的是,它是测试取消的唯一代码部分。即使ReportProgress 是异步的,您仍然需要UI 线程来处理取消按钮单击事件并将工作标记为CancellationPending

我建议您不要一次性在backgroundWorker1_ProgressChanged 中报告太多,或者考虑将向textBox1 添加项目的报告作为批次

这样您就不会淹没您的消息泵,并且您的应用程序将更具响应性。

更改您的 backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) 以使用 batches 作为:

var builder = new StringBuilder();

for (var i = 0; i < 1000; i++)
{
    if (cV1 == 101) incrementor1 = -1;
    if (cV1 == 0) incrementor1 = 1;

    if (cV2 == 101) incrementor2 = -1;
    if (cV2 == 0) incrementor2 = 1;

    if (cV1 > cV2)
    {
        builder.Append("ID: (" + i + ") CV1: (1): [" + cV1 + "] CV2: (0) [" + cV2 + "]\n");
    }
    else
    {
        builder.Append("ID: (" + i + ") CV1: (0): [" + cV1 + "] CV2: (1) [" + cV2 + "]\n");
    }

    cV1 += incrementor1;
    cV2 += incrementor2;
}

textbox1.AppendText(builder.ToString());

【讨论】:

  • 好吧,如果我将代码放在 worker.ReportProgress 上的 backgroundWorker1_DoWork我有错误吗
  • 我非常怀疑这一点。你认为什么会更快?单个 AppendText()1000 x AppendText()s?大部分 CPU 时间都花在了 AppendText() 上,即使调用堆栈来自另一个线程(后台工作人员的 ReportProgress
【解决方案2】:

由于您的 ProgressChanged 处理程序非常忙于每次 ReportProgress 调用更新 UI 大约 1000 次,它将使用所有 UI 线程 CPU 时间并且该线程将永远无法处理取消按钮,因为它已经太忙了.

鉴于您使用BackgroundWorker,您应该真正在DoWork 处理程序中移动大部分ProgressChanged,并在后台线程中删除该睡眠。

这种方式,你使用BackgroundWorker没有任何意义。 UI 线程应该尽可能少,后台线程应该尽可能多。在您的情况下,在后台线程中休眠是没有意义的,因为后台工作不依赖于经过的时间。

移动代码后,您需要将数据发送回 UI 线程。但这很容易,因为ReportProgress 的重载允许将任何 .NET 对象传递给 UI 线程(假设后台工作程序是从 UI 线程创建的,这通常是这种情况)。另一方面,您只需将用户数据转换为适当的类型,并使用该信息将适当的文本附加到文本框。

使用改进的代码,您应该能够更快地处理这些数据。顺便说一句,StringBuilder 也应该用于构建字符串。事实上,它应该会对 UI 性能产生巨大影响,因为您更新 UI 文本的频率降低了 1000 次。

【讨论】:

    猜你喜欢
    • 2020-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-15
    • 1970-01-01
    • 2015-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多