【问题标题】:c# - Pass information to BackgroundWorker From UI during executionc# - 在执行期间从 UI 向 BackgroundWorker 传递信息
【发布时间】:2012-05-17 03:55:08
【问题描述】:

我有一个使用后台工作线程的 c# 应用程序,并且非常成功地从正在运行的线程更新了 UI。该应用程序涉及网络上的最短路径路由,我在 UI 上显示网络和最短路径,因为后台工作程序继续进行。我想允许用户在应用程序运行时通过使用滑块来减慢显示速度。

我发现这是一个建议,但它在 vb.net 中,我不清楚如何让它在 c# 中工作。

How can the BackgroundWorker get values from the UI thread while it is running?

我可以将滑块的值传递给后台工作者,如下所示:

// 启动异步操作。 延迟 = this.trackBar1.Value; backgroundWorker1.RunWorkerAsync(delay);

并在后台工作线程中使用它,但它只使用最初发送的值。当我在 UI 上移动滑块时,我不清楚如何从后台工作人员内部获取值。

我之前使用过多个线程和委托,但如果可以使用后台工作程序,我更喜欢它的简单性。

2012 年 5 月 10 日

感谢大家的回复。我仍然遇到问题,很可能是因为我的结构方式。网络路由的繁重计算在 TransportationDelayModel 类中完成。 BackgroundWorker_DoWork 创建此类的一个实例,然后将其启动。延迟在 TransportationDelayModel 中处理。

代码骨架如下:

在用户界面中:

 private void runToolStripMenuItem1_Click(object sender, EventArgs e)
    {
        if (sqliteFileName.Equals("Not Set"))
        {
            MessageBox.Show("Database Name Not Set");
            this.chooseDatabaseToolStripMenuItem_Click(sender, e);

        }

        if (backgroundWorker1.IsBusy != true)
        {
            // Start the asynchronous operation.
            delay = this.trackBar1.Value;
            // pass the initial value of delay
            backgroundWorker1.RunWorkerAsync(delay);
            // preclude multiple runs
            runToolStripMenuItem1.Enabled = false;
            toolStripButton2.Enabled = false;
        }

    }

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

        if (!backgroundWorkerLaunched)
        {
            // instantiate the object that does all the heavy work
            TransportationDelayModel TDM = new TransportationDelayModel(worker, e);               
            // kick it off
            TDM.Run(sqliteFileName, worker, e);
            backgroundWorkerLaunched = true;
        }


    }

TransportationDelayModel 构造函数是:

 public TransportationDelayModel(BackgroundWorker worker, DoWorkEventArgs e)
    {
        listCentroids = new List<RoadNode>();
        listCentroidIDs = new List<int>();
        listNodes = new List<RoadNode>();
        listNodeIDs = new List<int>();
        listRoadLink = new List<RoadLink>();
        roadGraph = new AdjacencyGraph<int, RoadLink>(true); // note parallel edges allowed
        tdmWorker = worker;
        tdmEvent = e;
        networkForm = new NetworkForm();
    }

所以我有了 tdmWorker,它允许我将信息传递回 UI。

TransportationDelayModel 的内部计算中,我休眠了延迟时间

  if (delay2 > 0)
                                    {
                                        tdmWorker.ReportProgress(-12, zzz);
                                        System.Threading.Thread.Sleep(delay2);
                                    }

所以问题似乎是如何将更新的滑块值从 UI 传递回在后台工作程序中执行的对象。我已经尝试了多种组合,有点颠簸,但无济于事,要么什么都没有发生,要么我收到一条消息,说不允许访问另一个线程上发生的事情。我意识到,如果我在 DoWork 事件处理程序中完成所有工作,那么我应该能够按照您的建议进行操作,但是要做到这一点太复杂了。

再次感谢您的建议和帮助。

2012 年 6 月 2 日

我已经通过两种方法解决了这个问题,但是我有一些问题。根据我对 R. Harvey 的评论,我构建了一个简单的应用程序。它由一个带有运行按钮、滑块和富文本框的表单组成。运行按钮启动一个后台工作线程,该线程实例化一个“模型”类的对象,该对象完成所有工作(我的 TransportationModel 的简化代理)。 Model 类只是将 100 行写入文本框,每行中的点数增加 1,每行之间的延迟取决于滑块的设置,以及行尾的滑块值,类似于这个:

.......58

.....................58

......................58

.......................51

.......................44

......................44

本练习的目标是能够在“模型”运行时移动表单上的滑块,并获得更改延迟(如上所示)。

我的第一个解决方案是创建一个 Globals 类来保存滑块的值:

class Globals
{
    public static int globalDelay;
}

然后,在表单中,每当滚动轨迹栏时,我都会更新此值:

private void trackBar1_Scroll(object sender, EventArgs e)
    {
        Globals.globalDelay = this.trackBar1.Value;
    } 

而在Model中,我只是拿起了全局的值:

 public void Run(BackgroundWorker worker, DoWorkEventArgs e)
 {

     for (int i = 1; i < 100; i++)
     {
         delay = Globals.globalDelay; // revise delay based on static global set on UI
         System.Threading.Thread.Sleep(delay);
         worker.ReportProgress(i);
         string reportString = ".";
         for (int k = 0; k < i; k++)
         {
             reportString += ".";
         }
         reportString += delay.ToString();
         worker.ReportProgress(-1, reportString);

     }
 }
}

这很好用。 我的问题是:这种方法有什么缺点吗,它看起来很容易实现,也很笼统。

第二种方法基于 R. Harvey 的建议,利用委托和调用。

我为代表创建了一个类:

 public class MyDelegates
{
    public delegate int DelegateCheckTrackBarValue(); // create the delegate here
}

在表格中,我创建:

  public int CheckTrackBarValue()
    {
        return this.trackBar1.Value;

    } 

Model 类现在有一个成员 m_CheckTrackBarValue

 public class Model 
{

#region Members

Form1 passedForm;
public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;      
#endregion Members

#region Constructor
public Model(BackgroundWorker worker, DoWorkEventArgs e, Form1 form)
{
    passedForm = form;
}

运行按钮启动后台线程时,通过调用窗体

private void button1_Click(object sender, EventArgs e) { if (backgroundWorker1.IsBusy != true) {

            backgroundWorker1.RunWorkerAsync();

        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {

        BackgroundWorker worker = sender as BackgroundWorker;

        if (!backgroundWorkerLaunched)
        {
            // instantiate the object that does all the heavy work
            Model myModel= new Model(worker, e, this);

            Model.m_CheckTrackBarValue = new MyDelegates.DelegateCheckTrackBarValue(this.CheckTrackBarValue);

            // kick it off
            myModel.Run(worker, e);
            backgroundWorkerLaunched = true;
        }
    }

最后,在Model中,对传入的表单调用Invoke方法,获取trackbar的值。 公共无效运行(BackgroundWorker 工人,DoWorkEventArgs e) {

     for (int i = 1; i < 100; i++)
     {
         int delay = (int)passedForm.Invoke(m_CheckTrackBarValue,null); // invoke the method, note need the cast here
         System.Threading.Thread.Sleep(delay);
         worker.ReportProgress(i);
         string reportString = ".";
         for (int k = 0; k < i; k++)
         {
             reportString += ".";
         }
         reportString += delay.ToString();
         worker.ReportProgress(-1, reportString);

     }
 }

这也有效。在我将成员变量设为静态之前,我一直收到错误消息,例如 公共静态 MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;

我对此解决方案的问题:与以前的版本相比,此解决方案有优势吗?我的实现方式是否让事情变得过于复杂?为什么 m_CheckTrackBarValue 需要是静态的。

对于这次编辑的篇幅,我深表歉意,但我认为问题和解决方案可能会引起其他人的兴趣。

【问题讨论】:

  • 您的用户卡(问题右侧和下方的浅蓝色框)用作您的签名--^^^。您可以自定义您的用户卡here
  • 有完整源代码的最终解决方案吗?

标签: c# backgroundworker


【解决方案1】:

您必须将TrackBar 对象传递给BackgroundWorker,而不是delaydelay 设置后不会更改。

为了简化所需的Invoke(),可以使用辅助方法,例如this one

Async.UI(delegate { textBox1.Text = "This is way easier!"; }, textBox1, true);

【讨论】:

  • 感谢您的回复。我将 trackbar 作为 backgroundWorker1.RunWorkerAsync(trackBar1) 传递。当我尝试使用传递的对象时(通过类似 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) int delay2 = ((TrackBar)e.Argument).Value 它抱怨“跨线程操作无效,控制 TrackBar1 从除了创建它的线程之外的线程。{ BackgroundWorker worker = sender as BackgroundWorker; int delay2 = ((TrackBar)e.Argument).Value;
  • 啊。那么,你需要一个Invoke()msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
  • 我曾希望避免使用委托,因为后台工作人员很简单,并且以非常直接的方式将所需的通信提供回 UI。你知道这是一个非此即彼的提议,还是可以安全地与后台工作人员一起使用委托?
  • 如果它抱怨跨线程调用,您将需要Invoke()Control.Invoke()。语法并不像乍一看那样压抑。 BackgroundWorker 不关心所需的代表。 Have a look here 获取一些提示。
  • 谢谢。我以前在其他项目中使用过调用。我的计划是使用后台工作程序构建一个非常简化的应用程序,并测试调用方法。我打算让委托在 UI 论坛上拥有一个方法的签名,该方法将返回轨迹栏的整数值,以便我可以在后台工作线程上获取它。
【解决方案2】:

我假设您已经熟悉跨线程调用来更新 UI。因此,解决方案非常简单:在您的工作线程中,每次迭代后,调用 UI 以获取滑块拇指位置。

【讨论】:

  • 感谢您的回复。我使用 ReportProgress 更新 UI,但我不确定如何获取从 UI 返回的滑块的位置。
【解决方案3】:

要使用后台工作程序,请向DoWork 属性添加一个方法,如下所示:

this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);

DoWork方法中,需要检查设置更新延迟的变量。

这可以是包含表单或 UI 控件的整数字段,也可以是 TrackBar 本身。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多