1

Edit3:

I'm aware that the question is similar to others. The main difference, which isn't covered in those other questions is the fact that I need to achieve this with two seperate classes. I.e., starting the BGW in MainWindow and running the DoWork-method with the time consuming function in ConDB.

I have two classes MainWindow and ConDB. With a buttonclick I call a method in ConDB, which does some time consuming stuff. Now I need my progressbar to update with every iteration of my loop.

For this, I have the following code with a backgroundworker:

  private void btnConvert_Click(object sender, RoutedEventArgs e)
    {
       ConDB con = new ConDB();
       date = tbDatum.Text;

        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
        worker.DoWork += new DoWorkEventHandler(con.worker_DoWork);
        worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
        worker.RunWorkerAsync();

    }

RunWorkerAsync() will call my DoWork-Method in ConDB, hence con.worker_DoWork

In ConDB I call mw.worker.ReportProgress(progress) to update the progressbar which, as you can imagine, throws an InvalidOperationException:

The calling thread cannot access this object because a different thread owns it.

Now I read a lot about using Dispatcher.CurrentDispatcher.BeginInvoke since you can't access the UI thread from a backgroundworker thread. So far so good.

My problem now is, that I have no idea how to solve this issue with Dispatcher, since I dont know where and how to write this code. Also, I'm not sure if I'm doing things correctly with the backgroundworker. Do I need to define and instantiate it in my ConDB-class or can I keep it like this?

Any ideas, suggestions and help to educate me are much appreciated!

Edit:

public void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            pbProgressBar.Value = e.ProgressPercentage;
        }

throws the exception.

Edit2:

So to provide a hopefully reproducable example, here's the sample code.

I call this method from MainWindow:

        private void btnConvert_Click(object sender, RoutedEventArgs e)
    {
       ConDB con = new ConDB();

        con.test(); 
    }

Which would be this (in other class ConDB):

        public void test()
    {

        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
        worker.RunWorkerAsync();


    }

Which then calls the DoWork-method where I update my progressbar:

     public void worker_DoWork(object sender, DoWorkEventArgs e)
     { 
        for (int i = 0; i < 1000; i++)
        {
          percent++;
          progress = percent * 100 / rows.Count();
          worker.ReportProgress(progress); 
        }
      }

ReportProgress then invokes this:

        public void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        mw.pbProgressBar.Value = e.ProgressPercentage;
    }

My aim at first was to start the backgroundworker in MainWindow but to make my life easier and to test, I now call the method test, which starts the BGW.

Allix
  • 170
  • 2
  • 17
  • Are you updating the progressbar inside worker_ProgressChanged using e.g. this.Dispatcher.Invoke(() => this.progressBar.Value = progress)? – kkirk Jul 12 '17 at 09:56
  • 1
    @kkirk the `ProgressChanged` event runs on the UI thread. It doesn't require `Invoke` or `BeginInvoke`. – Panagiotis Kanavos Jul 12 '17 at 09:59
  • 1
    @Allix where does the error occur? Post the code that *actually* throws the error. Besides, BGW is obsolete. All of its functionality and much more is available, with a simpler API, through tasks, `async/await`, `Progress< T>` etc – Panagiotis Kanavos Jul 12 '17 at 10:01
  • 1
    `which, as you can imagine, throws an InvalidOperationException:` No I can't. That's how BGW is supposed to work. That's how the documentation examples and everyone's code for the last 15+ years work. You call `ReportProgress` from inside the `DoWork` handler and the `ProgressChanged` event is raised in the UI thread. Post your code – Panagiotis Kanavos Jul 12 '17 at 10:04
  • @PanagiotisKanavos Doesn't it run on the thread that started the worker? From the example, you're probably right since it's in a btn click event handler, but in general it might be more correct to say simply "the calling thread". – kkirk Jul 12 '17 at 10:04
  • @kkirk obviously, you can create event-like names for functions, and call them with Task.Run just to confuse others. Or because someone found a regurgitated 15-year old article about using BGWs instead of a thread and somehow managed to create a BGW on a background thread. In 99.95% of cases though, we are talking about the UI thread – Panagiotis Kanavos Jul 12 '17 at 10:08
  • @Panagiotis Kanavos I apologize if it came off the wrong way - I merely wanted to check a subtlety, since I just searched for it and couldn't find any information about it. I didn't know that background worker delegated the work to the caller, and you're absolutely right that it's clear from the example that it would be the UI thread in this example. – kkirk Jul 12 '17 at 10:11
  • 1
    @kkirk "no information"??? [The class documentation](https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx) is very extensive and contains multiple examples. – Panagiotis Kanavos Jul 12 '17 at 10:14
  • @PanagiotisKanavos `public void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { pbProgressBar.Value = e.ProgressPercentage; }` throws the exception here. – Allix Jul 12 '17 at 10:17
  • @PanagiotisKanavos So you're suggesting that I should leave the Backgroundworker out and try the approach with the API you've mentioned? – Allix Jul 12 '17 at 10:20
  • From what I remember from the BGW captures Current SynchronizationContext in its Run method, not constructor, and then posts the Events using that. So, it will actually report to whoever started it, not to the 'current ui thread' nor to whom created it. By the way, thinking about one/global "UI thread" is a slippery thing.. you can easily have more than one ui thread. – quetzalcoatl Jul 12 '17 at 10:21
  • FYI: Just found some case when BGW seems to mix things - https://social.msdn.microsoft.com/Forums/en-US/e61819bd-dcb6-4d68-a490-1b3ad70147af/backgroundworker-calls-runworkercompleted-event-on-worker-thread?forum=wpf and see answer from Steve .... but I'd be somewhat surprised if that's would be the case here – quetzalcoatl Jul 12 '17 at 10:24
  • but then.. if SynchronizationContext **is null** for some reason.. write `var foo = SynchronizationContext.Current;` just before `worker.RunAsync()`, place a break point on `worker.RunAsync` and see what is in `foo`. It if turns out that it is NULL, here's your problem. You will have to initialize it at app's startup, or at least before running BGW. – quetzalcoatl Jul 12 '17 at 10:28
  • @Allix I've just tested the code you posted as is (except the rows.Count() statement which I substitued for a fixed value) and it works fine. What is rows.Count() though - could that be the problem? I.e. that it's enumerating a query or some other object which is not accessible by the background thread? Could you try with a fixed value instead to check it? EDIT: Well, I just realized that doesn't make much sense actually, since that's not the line that's failing. But go ahead and test with a fixed value anyways, since that version works for me. – kkirk Jul 12 '17 at 11:32
  • If you were creating the `BackgroundWorker` and calling `RunWorkerAsync()` in the UI thread in the first place, you wouldn't even have this problem. One of the main reasons for using `BackgroundWorker` is because it's specifically designed to avoid the cross-thread issue, by raising events other than `DoWork` in the UI thread where you start it. So you must be using it from some other thread. That said, WPF will solve the problem for you if you use data binding to control the `ProgressBar.Value` value; you can change the model's value, and WPF will automatically marshal to the UI thread – Peter Duniho Jul 12 '17 at 20:59
  • @kkirk That's odd. I tried it with a fixed value but as you guessed, this wasn't the problem. @PeterDuniho I guess you're right but the thing is, that i want to keep the time consuming function in that seperate class and not in `MainWindow`. That's why the `DoWork`-method ist inside the class ConDB. You think this is the actual problem? – Allix Jul 13 '17 at 06:23

0 Answers0