BackgroundWorkerThread访问一个线程

时间:2009-05-12 18:32:10

标签: c# windows winforms

我在win form apps中大部分时间使用BackgroundWorker来显示进度,因为我正在获取数据。我的印象是Work_completed保证在主UI线程上执行,但事实并非如此。如果我们创建一个线程并在其中调用worker.RunWorkerAsync,如果我们尝试更新任何gui控件,它就会中断。这是一个例子

private void StartButton_Click(object sender, EventArgs e)
{
    Thread thread1 = new Thread(new ThreadStart(PerformWorkerTask));
    _worker = new BackgroundWorker();
    thread1.Start();
}

public void PerformWorkerTask()
{
    _worker.DoWork += delegate
    {
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(100);
        }
    };

    _worker.RunWorkerCompleted += delegate
    {
        // this throws exception
        MessageLabel.Text = "Completed";
    };
    _worker.RunWorkerAsync();

 }

在这种情况下,我们如何让后台工作者工作?

7 个答案:

答案 0 :(得分:1)

从你的例子中很难看出Thread(thread1)有什么好处,但是如果你真的需要这个thread1那么我认为你唯一的选择就是使用MainForm.Invoke()来执行RunWorkerAsync()(或者主线程上的一个小方法。

已添加:您可以使用以下内容:

Action a = new Action(_worker.RunWorkerAsync);
this.Invoke(a);

答案 1 :(得分:1)

RunWorkerAsync通过从调用它的线程中获取SynchronizationContext来实现其线程同步魔术。然后它保证事件将根据它获得的SynchronizationContext的语义在正确的线程上执行。对于WindowsFormsSynchronizationContext,即使用WinForms时自动使用的情况,通过发布到启动操作的线程的消息队列来同步事件。当然,这对你来说都是透明的,直到它破裂。

编辑:您必须从UI线程调用RunWorkerAsync才能使其正常工作。如果您不能以任何其他方式执行此操作,最好的办法是在控件上调用操作的开头,以便在UI线程上启动worker:

private void RunWorker()
{
    _worker = new BackgroundWorker();

    _worker.DoWork += delegate 
    {
        // do work
    };

    _worker.RunWorkerCompleted += delegate 
    {
        MessageLabel.Text = "Completed";
    };

    _worker.RunWorkerAsync();
}

// ... some code that's executing on a non-UI thread ...
{
    MessageLabel.Invoke(new Action(RunWorker));
}

答案 2 :(得分:1)

听起来问题只是你想要对GUI组件进行更改,而你实际上并不确定你是否在GUI线程上。 Dan发布了一种安全设置GUI组件属性的有效方法,但我发现以下快捷方法最简单:

            MessageLabel.Invoke(
                (MethodInvoker)delegate
                {
                    MessageLabel.Text = "Hello World";
                });

如果这种方法有任何问题,我想了解它们!

答案 3 :(得分:0)

在这里介绍的代码中,您将在与UI线程不同的线程中添加BackgroundWorker事件的委托。

尝试在主UI线程中添加事件处理程序,你应该没问题。

答案 4 :(得分:0)

我相信BackgroundWorker旨在自动使用新线程。因此,创建一个新线程只是为了调用RunWorkerAsync是多余的。您正在创建一个线程只是为了创建另一个线程。可能发生的是:

  1. 您从线程1(GUI线程)创建一个新线程;叫这个帖子2。
  2. 从线程2中,启动RunWorkerAsync,它本身会创建另一个线程;叫这个帖子3。
  3. RunWorkerCompleted的代码在线程2上运行,线程2是调用RunWorkerAsync的线程。
  4. 由于线程2与GUI线程(线程1)不同,因此会出现非法的跨线程调用异常。
  5. (以下建议使用VB而不是C#,因为这是我更熟悉的;我猜你可以弄清楚如何编写适当的C#代码来做同样的事情。)

    摆脱无关的新线索;只需声明_worker WithEvents,向_worker.DoWork和_worker.RunWorkerCompleted添加处理程序,然后调用_worker.RunWorkerAsync而不是定义自定义的PerformWorkerTask函数。

    编辑:要以线程安全的方式更新GUI控件,请使用以下代码(或多或少从this article from MSDN复制):

    delegate void SetTextCallback(System.Windows.Forms.Control c, string t);
    
    private void SafeSetText(System.Windows.Forms.Control c, string t)
    {
      if (c.InvokeRequired)
      {
        SetTextCallback d = new SetTextCallback(SafeSetText);
        d.Invoke(d, new object[] { c, t });
      }
    
      else
      {
        c.Text = t;
      }
    }
    

答案 5 :(得分:0)

您可以通过以下方式使现有代码正常工作:

this.Dispatcher.BeginInvoke(() => MessageLabel.Text = "Completed")

而不是

MessageLabel.Text = "Completed"

您可能遇到跨线程数据访问问题,因此您必须确保在UI线程上访问MessageLabel的属性。这是一种方法。其他一些建议也是有效的。问自己的问题是:为什么要创建一个除了创建BackgroundWorker线程之外什么都不做的线程?如果有原因,那么很好,但是从你在这里显示的内容来看,没有理由你无法从事件处理程序创建和启动BackgroundWorker线程,在这种情况下,由于RunWorkerCompleted事件,不存在跨线程访问问题handler将在UI线程上调用其委托。

答案 6 :(得分:0)

处理这些通用问题的最佳方法是处理一次。在这里,我发布了一个包含backgroupdworker线程的小类,并确保workcompleted总是在UI线程上执行。


using System.Windows.Forms; namespace UI.Windows.Forms.Utilities.DataManagment { public class DataLoader { private BackgroundWorker _worker; private DoWorkEventHandler _workDelegate; private RunWorkerCompletedEventHandler _workCompleted; private ExceptionHandlerDelegate _exceptionHandler; public static readonly Control ControlInvoker = new Control();

     public DoWorkEventHandler WorkDelegate
    {
        get { return _workDelegate; }
        set { _workDelegate = value; }
    }

    public RunWorkerCompletedEventHandler WorkCompleted
    {
        get { return _workCompleted; }
        set { _workCompleted = value; }
    }

    public ExceptionHandlerDelegate ExceptionHandler
    {
        get { return _exceptionHandler; }
        set { _exceptionHandler = value; }
    }

    public void Execute()
    {
        if (WorkDelegate == null)
        {
            throw new Exception(
                "WorkDelegage is not assinged any method to execute. Use WorkDelegate Property to assing the method to execute");
        }
        if (WorkCompleted == null)
        {
            throw new Exception(
                "WorkCompleted is not assinged any method to execute. Use WorkCompleted Property to assing the method to execute");
        }

        SetupWorkerThread();
        _worker.RunWorkerAsync();
    }

    private void SetupWorkerThread()
    {
        _worker = new BackgroundWorker();
        _worker.WorkerSupportsCancellation = true;
        _worker.DoWork += WorkDelegate;
        _worker.RunWorkerCompleted += worker_RunWorkerCompleted;

    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if(e.Error !=null && ExceptionHandler != null)
        {
            ExceptionHandler(e.Error);
            return;
        }
        ControlInvoker.Invoke(WorkCompleted, this, e);
    }
}

}

And here is the usage. One thing to note is that it exposes a static property ControlInvoker that needs to be set only once (you should do it at the beginning of the app load)

Let's take the same example that I posted in question and re write it

DataLoader loader = new DataLoader(); loader.ControlInvoker.Parent = this; // needed to be set only once

private void StartButton_Click(object sender, EventArgs e) {

Thread thread1 = new Thread(new ThreadStart(PerformWorkerTask));
_worker = new BackgroundWorker();
thread1.Start();

}

public void PerformWorkerTask() {

loader.WorkDelegate = delegate { // get any data you want for (int i = 0; i < 10; i++) { Thread.Sleep(100); } }; loader.WorkCompleted = delegate { // access any control you want MessageLabel.Text = "Completed"; }; loader.Execute();

}

干杯