当只调用一次RunWorkerAsync时,BackgroundWorker的DoWork会被调用两次?

16

我在一个类中创建了一个后台工作器,它可以正常工作。但是,如果我调用它并等待其运行结束,第二次调用它时,它将重复执行相同的过程。

我认为 bw.DoWork += 存在问题。

private void button1_Click(object sender, EventArgs e)
{
    nptest.test.start("null", "null");    
}


namespace nptest
{
    class test
    {
        public static void start(string str, string strb)
        {
            if (bw.IsBusy != true)
            {
                bw.WorkerSupportsCancellation = true;
                bw.DoWork += (obj, e) => bw_DoWork(str, strb);
                bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
                bw.RunWorkerAsync();
            }
        }
        private static BackgroundWorker bw = new BackgroundWorker();
        private static void bw_DoWork(string str, string strb)
        {
            System.Windows.Forms.MessageBox.Show("initializing BackgroundWorker");
        }
        private static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled == true))
            {
                Console.WriteLine("Canceled");
            }
            else if (!(e.Error == null))
            {
                Console.WriteLine("Error: " + e.Error.Message);
            }
            bw.Dispose();

        }
    }
}

问题已解决

  class test
    {
        private static List<object> arguments = new List<object>();

        // initializing with program startup
        public static void bwinitializing()
        {
            bw.WorkerSupportsCancellation = true;
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        }

        public static void start(string str, string strb)
        {
            if (bw.IsBusy != true)
            {
                arguments.Clear();
                arguments.Add(str);
                arguments.Add(strb);
                bw.RunWorkerAsync(arguments);
            }
        }
        private static BackgroundWorker bw = new BackgroundWorker();
        private static void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            List<object> genericlist = e.Argument as List<object>;
            System.Windows.Forms.MessageBox.Show("BackgroundWorker " + genericlist[0]);

        }

1
听起来很合理,如果你调用一个函数两次,它会执行两次相同的操作。你有什么问题?为什么你认为这种行为是不正确的? - Oded
如果您调用函数两次,它将执行两次代码。还能期望什么? - Tigran
我只点击了一次按钮,为什么它会执行两次进程? - User6996
@Power-Mosfet 我认为一个更合理的标题/问题描述应该是“当只调用一次RunWorkerAsync时,BackgroundWorker的DoWork被调用两次?”如果是这样,请考虑使用和/或调整它以更好地表示手头的问题。我没有自己进行编辑,因为我不确定这是否是正在观察到的问题。 - user166390
如果您通过设计器添加后台工作器,它会自动添加.DoWork +=赋值。如果您在代码中再次添加它,那么将导致DoWork函数被调用两次。 - keerz
显示剩余2条评论
7个回答

16
我怀疑不小心添加了多个DoWork事件。
也就是说,每次调用start方法时都会注册一个新的DoWork事件处理程序。这将会“添加”而不是替换现有的DoWork处理程序。因此,随后会调用多个DoWork处理程序,例如1、2、3等。
// creates a NEW delegate and adds a NEW handler
bw.DoWork += (obj, e) => bw_DoWork(str, strb);

我建议在这里不要使用闭包,而是使用方法组(带有隐式转换为委托)并将数据传递给RunWorkerAsync调用(有一个带有数据参数的表单)。

RunWorkerCompleted +=行没有这个问题,因为它从方法组传递了一个委托(保证始终计算为相同的委托对象1)。因此,对于该行的重复+=调用替换处理程序。


例子:

class MyData {
   public string StrA { get; set; }
}

// These only need to be setup once (and should be for clarity).
// However it will be "ok" now if they are called multiple times
// as, since the delegates are the same, the += will
// act as a replacement (as it replaces the previous delegate with itself).
bw.WorkerSupportsCancellation = true;
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;

// Pass data via argument
bw.RunWorkerAsync(new MyData {
    StrA = str,
});

void bw_DoWork (object sender, DoWorkEventArgs e) {
    var data = (MyData)e.Argument;
    var str = data.StrA;
    // stuff
}

1 我不确定它是否保证引用相等性,但使用这种方法允许从方法组的委托稳定地调用+=-=,即使是通过new DelegateType(MethodGroup)获得的。

关于我在主帖中的评论:如果从未创建它们的线程上访问UI元素,则会出现“跨线程操作异常”。我认为这种使用消息框是“可以”的(当没有从另一个线程创建所有者时),但在BackgroundWorker的DoWork中访问UI的做法通常是可疑的。


此外,在这里不要调用bw.Dispose();应该在拥有容器或上下文的情况下进行处理。在这种情况下,它看起来很好并且无害,但只有在那个BGW实例再也不会被使用时才这样做。从事件处理程序中调用它也是可疑的,因为BGW仍然处于“活动”状态。

@Power-Mosfet,这是我希望上述建议发生的事情 :) 为了澄清,DoWork被调用多次,对于每个发生的点击事件? - user166390
@Power-Mosfet 我上面的修改应该能确保不会发生这种情况。请确保仅有一行添加“DoWork”处理程序是bw.DoWork += bw_DoWork;,而且没有另一个隐藏在某个棘手地方的DoWork += ..。如果进行了此修改,则DoWord+=也可以(并且可能应该)移动到test类构造函数中。同时,请确保成功重新构建了项目并运行了正确的可执行文件——这可能是一个棘手的问题!此外,请参见我稍后关于(不)在此处使用Dispose()的说明... - user166390
我创建了一个新的类,复制并粘贴了您的代码片段,成功地重新构建了项目,但仍然出现相同的效果。我还删除了Dispose()方法。 - User6996
@Power-Mosfet - 你刚才是把整个代码复制粘贴到“Start”方法里了吗?你有阅读并遵循这个答案中的“//”注释吗? - Aaronaught

15

我遇到了与上面评论者"Power-Mosfet"相同的问题,最后通过添加new BackgroundWorker()并将其赋值给全局bw变量来解决问题。

代码如下,从:

private BackgroundWorker gBgwDownload;

private void yourFunction_bw(xxx)
{
    // Create a background thread
    gBgwDownload.DoWork += bgwDownload_DoWork;
    gBgwDownload.RunWorkerCompleted += bgwDownload_RunWorkerCompleted;
    //omited some code
    gBgwDownload.RunWorkerAsync(paraObj);
}

至:

private BackgroundWorker gBgwDownload;

private void yourFunction_bw(xxx)
{
    // Create a background thread
    gBgwDownload = new BackgroundWorker(); /* added this line will fix problem */
    gBgwDownload.DoWork += bgwDownload_DoWork;
    gBgwDownload.RunWorkerCompleted += bgwDownload_RunWorkerCompleted;
    //omited some code
    gBgwDownload.RunWorkerAsync(paraObj);

}

crifan,你是个英雄,还帮我解决了问题。 - Lynchie

5

还有一个原因。在生成的代码 InitializeComponent() 中查找 DoWorkEventHandler。如果您是通过组件 UI 属性生成它并自己注册的。

因为如果您再次注册它,它不会覆盖先前的注册,而是会添加另一个事件并调用两次。


3
在我的情况下,BackgroundWorker运行了两次,因为在我的表单构造器类中,我声明了DoWork、ProgressChanged和RunWorkerCompleted事件处理程序,但这些已经被Visual Studio 2013在该表单类的设计器部分中声明了。
因此,我只需删除我的声明即可正常工作。

对我来说也是一样,我从互联网上复制粘贴了“addHandler”行,这导致了多次调用。删除这些行就解决了问题。 - Pierre

0

谢谢...这段代码运行得很好...为BackgroundWorker创建新实例是个好主意... 现在我们可以在for/while循环中调用此函数,并且可以运行多个backgroundworker进程。

我编写的代码如下: 当按钮点击完成时...在不干扰主线程流的情况下...多个进程将在后台运行... 我只是使用了消息框来弹出...但我们可以在"bgwDownload_DoWork"函数中运行耗时的过程...并且会创建多个进程...这里我们不需要检查BackgroundWorker是否繁忙...

private void button1_Click(object sender, EventArgs e)
{
   for (int i = 0; i < 3; i++)
     yourFunction_bw(i);

 }
private BackgroundWorker gBgwDownload;

private void yourFunction_bw(int i)
{
    // Create a background thread
    gBgwDownload = new BackgroundWorker(); // added this line will fix problem 
    gBgwDownload.DoWork += bgwDownload_DoWork;
    gBgwDownload.RunWorkerAsync(i);

}

private void bgwDownload_DoWork(object sender, DoWorkEventArgs e)
{
  int stre = (int)e.Argument;
  MessageBox.Show(stre.ToString ()); // time taken process can be added here
}

0

我从设计师中删除了控件,并在代码中实例化了一个新的WorkerProcess:

例如: var bwProcess = new BackgroundWorker();

bwProcess.DoWork += new DoWorkEventHandler(bwProcess_DoWork);

bwProcess.RunWorkerCompleted += bwProcess_RunWorkerCompleted;


0
今天我遇到了这个问题,我在弹出窗口上放置了一个后台工作者来执行长时间运行的任务,当我注意到每次显示表单时会多次调用后台工作者的RunWorkerCompleted事件。
我的问题是在关闭表单后没有对其进行处理,这意味着每次显示表单时都会添加另一个处理程序到事件中。
解决方法是在使用完表单后对其进行处理。我想在这里提一下,因为当我寻找解决方案时就遇到了这个页面。

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