【问题标题】:BackgroundWorker with ProgressBar - Cross-thread operation not valid带有 ProgressBar 的 BackgroundWorker - 跨线程操作无效
【发布时间】:2014-07-03 11:23:24
【问题描述】:

我的表单应用程序中有服务调用。当我的按钮被点击时,我正在调用我的服务方法。这是我的代码块:

void listBox_SelectedValueChanged(object sender, EventArgs e)
    {
        if (listBox.SelectedItem.Equals("Demo"))
        {
            progressBar1.Visible = true;
            backgroundWorker1.RunWorkerAsync();
        }
    }

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {   
        ObservableCollection<FilterInfo> filterInfos = new ObservableCollection<FilterInfo>();
        FilterInfo myFilterObj = new FilterInfo("SUPERVISOR", userName);
        filterInfos.Add(myFilterObj);
        ObservableCollection<DEMOBec> demos = demoSvc.GetDemoByFilter(filterInfos, false);
        dt = new Converter<DEMOBec>().ConvertDataTable(demos.ToList());
    }

在调用服务之前,我让我的 ProgressBar(样式 = Marquee)可见。但由于跨线程问题,我无法使其在已完成的方法中不可见。

当我尝试在 BGWorker 的 Completed 事件中的 UI 线程中做某事时,

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        progressBar1.Visible = false;
    }

我遇到了一个异常:

跨线程操作无效:控件“Form1”从创建它的线程以外的线程访问。

我该如何处理这个问题?

【问题讨论】:

  • 您的变量“dt”似乎是全局变量,容易受到跨线程问题的影响。
  • 请检查我的回答中的编辑

标签: c# winforms progress-bar backgroundworker ui-thread


【解决方案1】:

你需要使用Invoke()方法

    private delegate void ToDoDelegate();
    void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Invoke(new ToDoDelegate(() => progressBar1.Visible = false));
    }

有关 Invoke() 的更多信息 http://msdn.microsoft.com/de-de/library/System.Windows.Forms.Control.Invoke(v=vs.110).aspx

【讨论】:

  • 不需要 Invoke() 再次它已经在 UI 上运行。检查Link
  • 感谢您的回复,这很有帮助。通过此操作(Invoke()),我是否创建了一个新线程来完成外部服务调用的工作?
【解决方案2】:

这是我一直喜欢使用的一小段代码,我不记得我从哪里得到的,很久以前我把它放在了我的 Dropbox 中。

public static class ControlExtensions
{
    public static TResult InvokeEx<TControl, TResult>(this TControl control,
                                               Func<TControl, TResult> func)
      where TControl : Control
    {
        return control.InvokeRequired
                ? (TResult)control.Invoke(func, control)
                : func(control);
    }

    public static void InvokeEx<TControl>(this TControl control,
                                          Action<TControl> func)
      where TControl : Control
    {
        control.InvokeEx(c => { func(c); return c; });
    }

    public static void InvokeEx<TControl>(this TControl control, Action action)
      where TControl : Control
    {
        control.InvokeEx(c => action());
    }

}

用法

this.InvokeEx( x => Progressbar1.Visible = false); //Note 'x' could be any variable

这样您就不必每次都输入所有内容,而且使用起来非常简单。只需将此权利添加到表单类的末尾(在最后一个括号关闭后)。您可以使用它来安全地执行任何跨线程操作。

【讨论】:

  • 谢谢你。这个课程非常有用。最好的问候。
【解决方案3】:

在 UI 线程上修改 ProgressBar 的可见性。

Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)
                    delegate()
                    {
                        progressBar1.Visible = false;
                    });

【讨论】:

    【解决方案4】:

    我认为RunWorkerCompleted 事件中没有引发异常(如 cmets 所述)。我的建议是,您尝试在 DoWork 事件中访问 UI 组件的行会触发它。

    尝试将DoWork事件中需要的数据放入DoWorkEventArgs e。诸如this discussion 之类的 SO 上提供了大量如何做到这一点的示例。

    工作完成后,也可以使用事件参数向“主 UI 线程”提供修改/新数据,如下所示:

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
        // do some work
        e.Result = ... ; // modified/new data
    }
    

    并通过以下方式检索它:

    void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        var newData = (CastToWhatever) e.Result;
    }
    

    希望对你有帮助 =)

    编辑

    backgroundWorker1_RunWorkerCompleted() 中的代码绝对不是异常的来源。它构建了一个小而简单的例子来证明我的论点:

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync(textBox1.Text);
    }
    
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        var num = Int32.Parse(e.Argument.ToString()) + 1;
        backgroundWorker1.ReportProgress(num);
        e.Result = num;
    }
    
    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = (int)e.ProgressPercentage;
    }
    
    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        textBox1.Text = e.Result.ToString();
    }
    

    它有什么作用?在 WinForm 上有一个带有数字的TextBox,一个ProgressBar 和一个Button 来启动一个BackGroundWorker

    worker从TextBox获取数字(UI线程)DoWork将其增加1并将其保存为结果(新线程) .此外,它将增加的数量报告给ProgressBar(跨线程)

    最后RunWorkerCompleted读出结果并将新值存储在TextBox(UI线程)

    所以,请重新检查您的代码;-)

    【讨论】:

    • 我将服务结果返回给 e.Result 但我在 progressBar.Visibility = false; BGWorker 的 Completed 事件中的行?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-13
    • 2020-08-08
    • 2012-09-12
    • 1970-01-01
    • 2011-07-11
    相关资源
    最近更新 更多