简体   繁体   中英

BackgroundWorker with ProgressBar - Cross-thread operation not valid

I have service call in my form application. When my button has been clicked I am calling my service method. Here is my code block:

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());
    }

Before calling the service, I make my ProgressBar (Style = Marquee) visible. But I couldn't make it invisible in completed method because of Cross Thread problem.

When I tried to do something with in UI thread in BGWorker's Completed event,

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

I am getting an exception :

Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.

How can I handle this problem?

You need to use Invoke() method

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

more info for Invoke() http://msdn.microsoft.com/de-de/library/System.Windows.Forms.Control.Invoke(v=vs.110).aspx

Here's alittle piece of code that I always love to use, I don't remember where I got it from I put it in my Dropbox a long time ago.

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());
    }

}

Usage

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

This way you won't have to type all that out each time, and it's so simple to use. Just add this right to the end of your form class (after the last bracket closes). You can use this to preform any cross thread operations safely.

Modify the Visibility of the ProgressBar on the UI thread.

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

I think the exception isn't raised in the RunWorkerCompleted event (as in comments said). My suggestion is, that the lines you try to access UI cmponents in the DoWork event fires it.

Try to put the data you need in the DoWork event into DoWorkEventArgs e . Tons of examples how to do it are provided on SO like this discussion .

When the work has finished, provide the "main UI thread" the modified/new data by using event args, too, like this:

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

and retrieve it in the following way:

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

Hope it helps =)

EDIT

Your code in backgroundWorker1_RunWorkerCompleted() is definitly not the source of the exception. It built a little and simple example to demonstrate my argment:

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();
}

What it does? On the WinForm there is a TextBox with a number, a ProgressBar and a Button to start a BackGroundWorker .

The worker gets the number from the TextBox (UI thread) , DoWork increases it by 1 and saves it as result (new thread) . Additionally it reports the increased number to the ProgressBar (cross-thread) .

Finally RunWorkerCompleted reads out the result and stores the new value in the TextBox (UI thread) .

So, please re-check your code ;-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM