首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >滥用/滥用C# BackgroundWorker?

滥用/滥用C# BackgroundWorker?
EN

Code Review用户
提问于 2014-01-21 11:38:44
回答 2查看 7.2K关注 0票数 10

我已经完成了一个程序,它做了我想做的事情,但是我觉得我“做错了”,尽管它看起来足够有效。我准备了一个小示例,说明我觉得backgroundworker类处理得不对,我想看看我是否能够更干净地处理这个问题。

首先,我有一个带有1按钮、1 statusstrip、1 toolstripprogressbar和1 toolstriplabel的表单。我在运行statusstrio时更新backgroundworker项,并显示跨类的messageboxes。见下文。

Form1.cs

代码语言:javascript
复制
namespace StatusStripTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void prepareToRun()
        {
            if (!backgroundWorker1.IsBusy)
            {
                backgroundWorker1 = new BackgroundWorker();
                backgroundWorker1.DoWork += delegate
                {
                    Looping.loop(statusStrip1);
                };
                backgroundWorker1.RunWorkerAsync();
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            prepareToRun();
        }

    }
}

Looping.cs

代码语言:javascript
复制
namespace StatusStripTest
{
    public class Looping
    {
        public static void loop(StatusStrip strip)
        {
            Form1 Form1 = new Form1();
            string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
            while (true)
            {
                MessageBox.Show("Time to loop");
                foreach (char c in alphabet)
                {
                    StripHandler.UpdateProgress(strip, alphabet.IndexOf(c), alphabet.Length);
                    StripHandler.UpdateStatus(strip, c.ToString());
                }
            }

        }
    }

}

StripHandler.cs

代码语言:javascript
复制
namespace StatusStripTest
{
    public class StripHandler
    {
        public static void UpdateStatus(StatusStrip ss, String Status)
        {
            ss.Invoke((MethodInvoker)delegate
            {
                if ((ss.Items[1] as ToolStripStatusLabel) != null)
                {
                    ss.Items[1].Text = Status;
                }
            });
        }

        public static void UpdateProgress(StatusStrip ss, long position, long len)
        {
            ss.Invoke((MethodInvoker)delegate
            {
                if ((ss.Items[0] as ToolStripProgressBar) != null)
                {
                    ToolStripProgressBar tspb = (ToolStripProgressBar)ss.Items[0];
                    tspb.Minimum = 0;
                    tspb.Maximum = (int)len;
                    tspb.Value = (int)position;
                }
            });
        }
    }
}

我是不是不正确地通过了statusstrip

EN

回答 2

Code Review用户

回答已采纳

发布于 2014-01-21 20:04:27

这里有一种技术,可以在一个文件和一个类名下完成您的三个类所做的一切。Konrad的代码很好,但是下面的代码展示了如何完成您的任务。

由于您不需要更改BackgroundWorker的事件处理程序,所以在表单的构造函数中对它们进行编码:

代码语言:javascript
复制
private const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

public Form1() {
  InitializeComponent();
  backgroundWorker1 = new BackgroundWorker();
  backgroundWorker1.WorkerReportsProgress = true;
  backgroundWorker1.WorkerSupportsCancellation = true;
  backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
  backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}

我还将alphabet定义为常量,因为它永远不会改变。

现在,您需要知道什么是backgroundWorker_DoWorkbackgroundWorker_ProgressChanged

此版本最接近您发布的代码,使用的是字母表字符串上的foreach循环。每次访问下一个字符时,都必须提取该字符的索引,并且必须再次确定固定数组的长度:

代码语言:javascript
复制
private void backgroundWorker_DoWork_obsolete(object sender, DoWorkEventArgs e) {
  var obj = (BackgroundWorker)sender;
  while (!obj.CancellationPending) {
    foreach (char c in alphabet) {
      float calc = ((float)alphabet.IndexOf(c) / alphabet.Length) * 100;
      obj.ReportProgress(Convert.ToInt32(calc));
    }
  }
}

下面修改的版本将使用作为参数提供的值来创建字符串(因此,现在可以将此代码用于其他字符串值)。由于您的代码基本上只是遍历字符串的长度,所以我修改了这个版本以使用更高效的for(;;)循环(更有效,因为它不必每次提取索引或计算字符串长度):

代码语言:javascript
复制
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
  var obj = (BackgroundWorker)sender;
  string alphas = e.Argument.ToString();
  int len = alphas.Length;
  while (!obj.CancellationPending) {
    for (int i = 0; i < len; i++) {
      float calc = ((float)i / len) * 100;
      obj.ReportProgress(Convert.ToInt32(calc));
    }
  }
}

注意,在这两个示例中,我使用的是while (!obj.CancellationPending)而不是while(true)。现在,如果您想在表单中包含一个Cancel按钮,只需将其连接如下:

代码语言:javascript
复制
private void cancel_Click(object sender, EventArgs e) {
  backgroundWorker1.CancelAsync();
}

这将使您的BackgroundWorker循环停止。

现在,backgroundWorker_ProgressChanged例程很简单:

代码语言:javascript
复制
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
  toolstripprogressbar.Value = e.ProgressPercentage;
}

要执行这段代码,只需确保在调用toolstripprogressbar之前初始化RunWorkerAsync

代码语言:javascript
复制
private void prepareToRun() {
  if (!backgroundWorker1.IsBusy) {
    toolstripprogressbar.Minimum = 0;
    toolstripprogressbar.Value = 0;
    toolstripprogressbar.Maximum = alphabet.Length;
    backgroundWorker1.RunWorkerAsync(alphabet);
  }
}

private void button1_Click(object sender, EventArgs e) {
  prepareToRun();
}

一些额外的注意事项:

BackgroundWorker.ReportProgress被重载以接受object userState,而BackgroundWorker.ProgressChangedEventArgs包含这个UserState对象。因此,如果您想将实际项传递给名为lblProgress的Label控件,这是可能的:

代码语言:javascript
复制
private void backgroundWorker_DoWork2(object sender, DoWorkEventArgs e) {
  var obj = (BackgroundWorker)sender;
  string alphas = e.Argument.ToString();
  int len = alphas.Length;
  while (!obj.CancellationPending) {
    foreach (char c in alphas ) {
      float calc = ((float)alphas .IndexOf(c) / len ) * 100;
      obj.ReportProgress(Convert.ToInt32(calc), c);
    }
  }
}

private void backgroundWorker_ProgressChanged2(object sender, ProgressChangedEventArgs e) {
  toolstripprogressbar.Value = e.ProgressPercentage;
  lblProgress.Text = e.UserState.ToString();
}

如果你想变的花花公子,把BackgroundWorker.RunWorkerCompletedEventHandler:连接起来

代码语言:javascript
复制
  backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);

...add这段代码可以隐藏进度栏并启用Cancel:

代码语言:javascript
复制
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
  toolstripprogressbar.Visible = false;
  btnCancel.Enabled = true;
}

...and修改prepareToRun()语句:

代码语言:javascript
复制
private void prepareToRun() {
  if (!backgroundWorker1.IsBusy) {
    toolstripprogressbar.Minimum = 0;
    toolstripprogressbar.Value = 0;
    toolstripprogressbar.Maximum = alphabet.Length;
    backgroundWorker1.RunWorkerAsync(alphabet);
    if (backgroundWorker1.IsBusy) {
      toolstripprogressbar.Visible = true;
      btnCancel.Enabled = false;
    }
  }
}

你能看出我用了很多BackgroundWorkers吗?

更新:

另一个注意事项,基于您提供的评论。DoWorkEventArgs变量e有一个名为参数的对象变量,它对应于您在backgroundWorker1.RunWorkerAsync(any_object)中提供的任何输入。

这意味着any_object可以是这样的类:

代码语言:javascript
复制
class MyParameters {
  public int Minimum { get; set; }
  public int Index { get; set; }
  public int Maximum { get; set; }
  public string TextIn { get; set; }
  public string TextOut { get; set; }
}

要使用它,只需将其转换回传入的内容:

代码语言:javascript
复制
private const int BG_MSG_1 = 1;

private void backgroundWorker_DoWork3(object sender, DoWorkEventArgs e) {
  var obj = (BackgroundWorker)sender;
  var params = (MyParameters)e.Argument;
  params.Minimum = 0;
  params.Index = 0;
  params.Maximum = params.TextIn.Length;
  while (!obj.CancellationPending) {
    foreach (char c in params.TextIn ) {
      params.Index++;
      params.TextOut = string.Format("Processed {0}", c);
      obj.ReportProgress(BG_MSG_1, params);
    }
  }
}

注意,我已经重用了ReportProgress的int变量ProgressPercentage,作为在ProgressChanged事件处理程序中提供更多/更深层次选项的一种方法。

现在,您可以在主线程中执行任何需要的计算,方法是从MyParameters中提取UserState:

代码语言:javascript
复制
private void backgroundWorker_ProgressChanged3(object sender, ProgressChangedEventArgs e) {
  if (e.ProgressPercentage == BG_MSG_1) {
    var params = (MyParameters)e.UserState;
    float calc = ((float)params.Index / params.Maximum ) * 100;
    toolstripprogressbar.Value =  Convert.ToInt32(calc)
    lblProgress.Text = params.TextOut;
  }
}

现在,您只受您想要来回传递的任何类的复杂程度的限制--您可以创建一个类来传递数据,而另一个类用于传递数据。真的,全靠你了。

票数 6
EN

Code Review用户

发布于 2014-01-21 12:26:36

通常,向BackgroundWorker传递控件根本不应该是必要的。想一想,因为它只是有一些工作要做,仅此而已。然而,它当然可以报告其工作的进展情况。在设计BackgroundWorker类时考虑了这两种思想,DoWorkProgressChanged事件分别反映了这两种思想。还有一个专门的BackgroundWorker.ReportProgress方法来引发ProgressChanged事件。

这个事件应该在GUI级别上处理,这样它就可以访问控件,而不必将它们传递给worker。解决办法草案:

代码语言:javascript
复制
private void prepareToRun()
{
    if (!backgroundWorker1.IsBusy)
    {
        backgroundWorker1 = new BackgroundWorker();
        backgroundWorker1.DoWork += backgroundWorker_DoWork;
        backgroundWorker1.ProgressChanged += backgroundWorker_ProgressChanged;
        backgroundWorker1.RunWorkerAsync();
    }
}

public static void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    // use worker.ReportProgress(percent, someData) somewhere here

}

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // Update your statusStrip1 here according to data passed in e.ObjectState
}
票数 8
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/39733

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档