Thread problem, backgroundworker?

Phönixen

Member
Joined
Sep 11, 2019
Messages
5
Programming Experience
3-5
Word 2016, Visual Studio 2019
Hello everybody,
I hope, that you can help me with my problem.
My issue:
I have three folders with word-documents (.docx), each more than 100 documents.
Each folder contents documents with a different theme.
I want to select documents by different properties, e.g. words of the content.
With the start of the word-application I start a backgroundworker, that collects the important data of each document and stores them in a Dictionary(Of String, String).
All datasets are stored in an array of three ObservableCollections, one for each path/group of documents: 'Public Shared Dateilisten(2) As ObservableCollection(Of Dictionary(Of String, String))'
The array is part of a class 'Worddatei', that represents the properties of the word-files.
With myWorker.reportprogress I deliver each set of data ('myDataset') to the sub 'ReportProgress': 'Private Shared Sub ReportProgress(ByVal sender As System.Object, ByVal e As ProgressChangedEventArgs) Handles MyWorker.ProgressChanged'.
This sub adds the dataset to the correct ObservableCollection in the array 'Dateilisten()'.
Then it raises the public event 'Public Shared Event DateilisteAktualisiert(ProcessPercentage As Integer)'.
When I show the Window, eventually not all Word-Documents are loaded.
So the window shall show the analysed filenames and add each new filename to the corresponding listbox, when the 'ReportProgress'-event of the backgroundworker is raised.
This event is handled by a Sub of the class 'MainWindow.xaml.vb', which is a code-behind-class of a WPF-Window: 'AddHandler DateilisteAktualisiert, AddressOf PBarFortschritt_ChangeValue'.
In that moment when the event is raised, it is not possible to get or set values to the controls of the window, as I get the following error message: System.InvalidOperationException; The calling thread cannot get the object, because it is owned by another thread.
Please tell me, how I can solve the problem or identify the bug.

This is the code of the backgroundworker:

VB.NET:
Private Shared Sub Do_work(ByVal sender As System.Object, ByVal e As DoWorkEventArgs) Handles MyWorker.DoWork
   Thread.CurrentThread.Priority = ThreadPriority.Lowest
   Dim Index = 0
   Dim myDataset As Dictionary(Of String, String) ' = New Dictionary(Of String, String)
   Dim myPaths(2) As String
   myPaths(0) = My.Resources.Path0
   myPaths(1) = My.Resources.Path1
   myPaths(2) = My.Resources.Path2
   For Each Path In myPaths
      For Each element In Directory.GetFiles(Path)
         Index += 1
         If IO.Path.GetExtension(element) = ".docx" OrElse
            IO.Path.GetExtension(element) = ".docm" OrElse
            IO.Path.GetExtension(element) = ".doc" Then
            myDataset = Worddataset(element)
            MyWorker.ReportProgress(Index, myDataset)
         End If
         If MyWorker.CancellationPending Then
            e.Cancel = True
            Exit Sub
         End If
      Next
   Next
End Sub
        
Private Shared Function Worddataset(Fullname As String) As Dictionary(Of String, String)
   Worddataset = New Dictionary(Of String, String)
   Try
      Using Dokument = WordprocessingDocument.Open(Fullname, False)
         Worddataset.Add(PROP_FULLNAME, Fullname)
         '...
         Dokument.Close()
      End Using
   Catch ex As Exception
   End Try
End Function

And this is the 'ProgressChanged'-code of the class 'Worddatei' that represents the specific custom properties of the selected word-file, that contains all Datasets of the documents in a shared ObservableCollection-Array and that holds the backgroundworker-Subs:
(Status and myStatus represents the property that shows, which ObservableCollection of the array is the correct one.

VB.NET:
 Private Shared Sub ReportProgress(ByVal sender As System.Object, ByVal e As ProgressChangedEventArgs) Handles MyWorker.ProgressChanged
     Dim myDict = CType(e.UserState, Dictionary(Of String, String))
    Dim myStatus = Status(myDict)
    Dateilisten(myStatus).Add(myDict)
    RaiseEvent DateilisteAktualisiert(e.ProgressPercentage)
 End Sub

This is the code in the MainWindows-Sub, that is added to the Handler of 'DateilisteAktualisiert'
PBarFortschritt is the Processbar, that shall show the progress of the Backgroundworker.
But in this moment no control-property of the MainWindow is available.
It doesn't matter, if the Sub is attached to DateilisteAktualisiert or to the changed-Event of the ObservableCollection.

VB.NET:
Private Sub PBarFortschritt_ChangeValue(Anzahl As Integer)
    '...'   
        PBarFortschritt.Foreground = COLOR_GREEN.Brush
       '...'   
        PBarFortschritt.Value = Anzahl * 100 / _Total
        '...'
    End Sub

Thank you very much for your help
 

Phönixen

Member
Joined
Sep 11, 2019
Messages
5
Programming Experience
3-5
Ok, thank you for that information. You are right. The backgroundworker starts with the word-application, so that he can collect as much information as possible, before the WPF-Window is started by the user.
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
Solved. I have to use invoke.
Why are you using invoke, when you should be using report progress and sending the progress of the background worker to the user?
If I understand that would suggest the BackgroundWorker was not created in UI thread. When created in UI thread it also raise its events in UI thread ---
@JohnH, that's not entirely correct in the context in which you wrote that. I assume you meant what I am about to explain below. But for the sake of clarity, can you explain the above quoted line? A more correct way of wording that would be; this would mean that the handlers responsible for raising these events where created on a non UI thread, and that of the thread that launched the worker thread.

If a background worker is started from the UI thread, it does not run on the UI thread, but it runs on a separate thread. It is up to the developer to use the correct functionality ie methods, and properties to ensure the background worker reports its progress with BackgroundWorker.ReportProgress which raises the BackgroundWorker.ProgressChanged Event which is used for updating the user as progress commences and runs its cycle, and said developer should also periodically check for cancellation pending, should their be a call by the user to cancel the current operation. If the handlers for these events are created on the UI thread, then the OP can use these events to update the UI from the background worker without invoking.

On that note; if the background worker is doing work VIA its BackgroundWorker.DoWork, then this code from this event isn't running on the UI thread, and so this will require the developer to use the Invoke method to invoke the controls which are actually on the UI thread, if they're not using the report progress feature of the BackgroundWorker. However, this is a misuse of this component. And our OP would be best reading over the documentation for the BackgroundWorker Class (System.ComponentModel)
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
@Sheepings Yes, I meant the SynchronizationContext is set when RunWorkerAsync is called, so the thread you're in at that time determines which thread the events are posted to. Usually that means starting up in UI thread to take advantage of BackgroundWorkers event model.
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
Ah, I knew I was probably misinterpreting what you said, I just had to be sure what you were saying first. It was just how you wrote it; it sounded a bit abstruse. That makes sense now.
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
so the thread you're in at that time determines which thread the events are posted to.
If they're also initialised there. Yes (y)

@Phönixen why are you invoking? If you wish to invoke, you may as well use a single thread instead of a background worker. If you're not going to use the available events, properties methods by the background worker, then there is no point in using the worker in the first place.
 
Last edited:

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
Oh I stand corrected good sir. :p

I had to double check the msdn docs on that. So it does delegate back by itself.
A SendOrPostCallback object that wraps the delegate to be called when the operation ends.
You're on the ball tonight! lol
 

Phönixen

Member
Joined
Sep 11, 2019
Messages
5
Programming Experience
3-5
@Phönixen why are you invoking? If you wish to invoke, you may as well use a single thread instead of a background worker. If you're not going to use the available events, properties methods by the background worker, then there is no point in using the worker in the first place.
@Sheepings you are right.
I recapitulate my scenario:
- I develop a word-addin, that starts with the word-application
- This word-addin uses a WPF-app, that manipulates many worddocuments in three directories.
- The relevant information of these worddocuments is loaded in three observablecollections. These observablecollections are defined as 'shared' in the WPF-app.
- the loading of the relevant information consumes a lot of time.
- In my first approach I had to wait a while after starting the WPF, until all information was loaded.
- in my second approach I built that backgroundworker to collect the information and store it in the observablecollections, while the WPF was running and the responsible listbox filled with the incoming information. So the WPF was reacting to userinput, but the user had to wait anywhere, until his needed information was loaded.
- in my third approach I switched the backgroundworker from the WPF to the Word-AddIn, so that the collecting of the information started with loading the AddIn and not just with first starting of the WPF-window. When the user starts the WPF now, a bunch of information is present and the waitingtime in minimized. But now my problem began, and I asked for help.

In between I solved my problem with invoking. Obviously you are right, when I use the invoke, I don't need the backgroundworker any more.

Thank you for your feedback
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
132
Location
UK
Programming Experience
10+
Are you invoking your control from a new thread now, and have you replaced the BackgroundWorker with a new thread instead?

I am just trying to ascertain what solution you settled on, so I can best advise if your course of action was the best one to take.
 
Top Bottom