Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 719
  • Last Modified:

VB.NET BackgroundWorker not completing / updating progress correctly

My main form has a panel that can display various usercontrols.

One of the usercontrols reads and displays a file, and I cannot get the backgroundworker to complete correctly.  In addition to this the progress bar is not updating correctly (about half the correct percentage) although I believe this is probably related to the backgroundworker not completing correctly issue.

What happens is the file is read ok, and whilst being read the progess bar reflects about 50% of the progress; the cancel button is not displayed.  However, once the file is read, the progress bar jumps to 100% and the cancel button is displayed.  The cancel button stays enabled until I click it and the backgroundworker completes.  In debug, I noticed that the RunWorkerCompleted section is never invoked.

Here is my code:

Form1

BackgroundWorker1.RunWorkerAsync()


Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ProgressBar2.Value = e.ProgressPercentage
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        'Check the state of completion
        If Not (e.Error Is Nothing) Then
            'an error occured and stopped the calculation
            lblResult.Text = e.Error.Message
        ElseIf e.Cancelled Then
            'the process was cancelled by the user
            lblResult.Text = "Process cancelled"
        Else
            'the process completed normally
            lblResult.Text = "Complete"
        End If
        'Reset some other controls 
        
        PanelWait.Visible = False
        Me.Cursor = Cursors.Default
End Sub

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ProgressBar2.Value = e.ProgressPercentage
End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

        Dim returnvalue As Integer

        returnvalue = Load_UserControl_Screen()

End Sub

Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
        BackgroundWorker1.CancelAsync()
        PanelWait.Visible = False
End Sub

Open in new window


UserControl

Public Class UserControl_ReadFile

    Dim TextLine As String
    Dim LineLength As Integer
    Dim owner As Form1
    Dim Offset As Integer

    Dim start As Integer = 0
    Dim indexOfSearchText As Integer = 0
    Dim No_of_recs As Integer = 0
    Dim Total_recs As Integer = 0

    Public Sub SetParent(ByVal parent As Form1)

        owner = parent

    End Sub

Private Sub UserControl_ReadFile_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Load_Screen()

End Sub

Public Function Load_Screen()

        Read_Ahead()
        Read_File()

End Function

Public Sub Read_Ahead()

        Dim FileName As String = Textbox1.Text
        Dim objreader1 As New System.IO.StreamReader(FileName)

        Do While objreader1.Peek() <> -1

            If objreader1.ReadLine().Length > LineLength Then
                LineLength = objreader1.ReadLine().Length
            End If

            Total_recs = Total_recs + 1

        Loop

    End Sub

Public Sub Read_File()

    Dim FileName As String = Textbox1.Text
    Dim objreader1 As New System.IO.StreamReader(FileName)
    RichTextBox1.Text = ""

    Do While objreader1.Peek() <> -1

        TextLine = objreader1.ReadLine() & vbNewLine
        Write_Record()
        No_of_recs = No_of_recs + 1
        owner.BackgroundWorker1.ReportProgress((No_of_recs / Total_recs) * 100)

        If owner.BackgroundWorker1.CancellationPending = True Then
            Exit Do
        End If

    Loop

End Sub

Public Sub Write_Record()

        RichTextBox1.AppendText(TextLine)

End Sub

End Class

Open in new window


Can anyone help with this?
0
mondintator
Asked:
mondintator
  • 8
  • 7
  • 3
2 Solutions
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Line #34 is:

       returnvalue = Load_UserControl_Screen()

Where is the Load_UserControl_Screen() method?...
0
 
mondintatorAuthor Commented:
Sorry, I was removing redundant code.  Should be:

Form 1

BackgroundWorker1.RunWorkerAsync()


Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ProgressBar2.Value = e.ProgressPercentage
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        'Check the state of completion
        If Not (e.Error Is Nothing) Then
            'an error occured and stopped the calculation
            lblResult.Text = e.Error.Message
        ElseIf e.Cancelled Then
            'the process was cancelled by the user
            lblResult.Text = "Process cancelled"
        Else
            'the process completed normally
            lblResult.Text = "Complete"
        End If
        'Reset some other controls 
        
        PanelWait.Visible = False
        Me.Cursor = Cursors.Default
End Sub

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ProgressBar2.Value = e.ProgressPercentage
End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

        Dim returnvalue As Integer

        returnvalue = UserControl_ReadFile.Load_Screen()

End Sub

Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
        BackgroundWorker1.CancelAsync()
        PanelWait.Visible = False
End Sub

Open in new window


UserControl

Public Class UserControl_ReadFile

    Dim TextLine As String
    Dim LineLength As Integer
    Dim owner As Form1
    Dim Offset As Integer

    Dim start As Integer = 0
    Dim indexOfSearchText As Integer = 0
    Dim No_of_recs As Integer = 0
    Dim Total_recs As Integer = 0

    Public Sub SetParent(ByVal parent As Form1)

        owner = parent

    End Sub

Private Sub UserControl_ReadFile_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Load_Screen()

End Sub

Public Function Load_Screen()

        Read_Ahead()
        Read_File()

End Function

Public Sub Read_Ahead()

        Dim FileName As String = Textbox1.Text
        Dim objreader1 As New System.IO.StreamReader(FileName)

        Do While objreader1.Peek() <> -1

            If objreader1.ReadLine().Length > LineLength Then
                LineLength = objreader1.ReadLine().Length
            End If

            Total_recs = Total_recs + 1

        Loop

    End Sub

Public Sub Read_File()

    Dim FileName As String = Textbox1.Text
    Dim objreader1 As New System.IO.StreamReader(FileName)
    RichTextBox1.Text = ""

    Do While objreader1.Peek() <> -1

        TextLine = objreader1.ReadLine() & vbNewLine
        Write_Record()
        No_of_recs = No_of_recs + 1
        owner.BackgroundWorker1.ReportProgress((No_of_recs / Total_recs) * 100)

        If owner.BackgroundWorker1.CancellationPending = True Then
            Exit Do
        End If

    Loop

End Sub

Public Sub Write_Record()

        RichTextBox1.AppendText(TextLine)

End Sub

End Class

Open in new window

0
 
CodeCruiserCommented:
You are calling the LoadScreen method manually and it is also called in the Load method of the user control.

Also, you have two progresschanged event handlers.
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
Mike TomlinsonMiddle School Assistant TeacherCommented:
You're correctly calling ReportProgress() for the ProgressBar, but not using the BackgroundWorker() for WriteRecord()?  You can pass the value of TextLine out using ReportProgress as well.  You also shouldn't be directly clearing RichTextBox1 from the thread at line #53.

Even if you instantiate the UserControl properly, and start the method from the right place, I'm not sure that threading is helping you at all here.  You're asking the main UI thread to update itself for every single line that is being read in the secondary thread; in real-time.  This means that the main UI thread has to do just as much work as the thread, if not more, since the main UI thread has to marshal the call and update the screen.

What exactly are you trying to accomplish here with those lines of text?...
0
 
mondintatorAuthor Commented:
I'm just trying to read a file and while the file is reading display a please wait panel.  I'm doing this by getting my background worker to read the file and to update the wait panel with the progress.
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
How big is the file?  (approx how many lines?)
How long does it take to read the file?

If the file is huge, then you probably don't want the whole thing displayed...

If it is relatively small, then just do:
Public Class Form1

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        RichTextBox1.Lines = System.IO.File.ReadAllLines(TextBox1.Text)
    End Sub

End Class

Open in new window

0
 
mondintatorAuthor Commented:
The files varies in size - can be just a few hundred lines, or hundreds of thousands.

I can't get the background worker going at all now - it falls over with cross-thread.
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Right...you'll get the cross-thread error when attempting to access elements directly from the background thread as I described earlier.  In your second posting, your causing errors at line #53 and line #72.

"... or hundreds of thousands"  

You definitely don't want to load a file with hundreds of thousands of lines into a RichTextBox.  It will take forever to load, be horribly inefficient, and be of little use to the end user.

How is this file and the RichTextBox going to be used by the end user?  For large files like that, you'd probably want to use some kind of paging mechanism to only load small portions of it at a time.  The user can then click "next page" to grab the next section, etc...
0
 
mondintatorAuthor Commented:
Idle_Mind - I'm impressed.  You're correct - it cross-threads at line #53 and #72 as you say.  How did you know that because I still can't work out what the issue is.
I know that it must be something to do with the load form being called from the DoWork and from the Form, but why does it have an issue with those two specific lines?
Something to do with the reference of RichTextBox?  Why?
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
You're not supposed to directly write to, or change UI controls from a background thread, which those lines are clearly doing.

As CodeCruiser pointed out earlier, you shouldn't be firing off the work in the Load() event:
Private Sub UserControl_ReadFile_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Load_Screen() '  <--- don't start the work here!...

End Sub

Open in new window


It's also unclear where and when this UserControl is being created.

I'm sure we can get it working properly, but with the postings so far, it's been hard to tell exactly who is creating who in what order...
0
 
mondintatorAuthor Commented:
I've built a form with a panel.  When the form loads a user control populates the panel with a selection of options.  One of the options is to read a file.  If the user selects this option, it brings into the panel another user control containing information about the file (e.g. browse for a file).  Once a file is selected and the user selects the next button, the next user control reads the file and displays it in the panel.

It is this last user control that I would like to have a 'please wait' message whilst the textbox is populating the contents of the file.  Actually I have a please wait, but it's a form and does not show the progress bar.

So I thought by using the background worker to read the file, I can display a 'wait' panel with a progress bar.

How can I get the background worker to read a file if you are not supposed to directly write to UI controls from a background thread?

I have removed the 'load_screen' line of code as this was being executed when I made the user control active.
0
 
CodeCruiserCommented:
>How can I get the background worker to read a file if you are not supposed to directly write to UI controls from a background thread?

Reading file has nothing to do with accessing UI. Reading file is a background task. What you can do is read the file in the DoWork (into some variable) and then use the RunWorkerCompleted event to push that to UI.

DoWork executes on separate thread. ProgressChanged and RunworkerCompleted execute on UI thread.
0
 
mondintatorAuthor Commented:
What would you recommend I read the file into (bearing in mind the files can be quite large and wouldn't want to store in an array)?  Can I populate a textbox and then only display the textbox from the runworkcompleted?
I haven't seen any examples of how I can do this.
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
"... bearing in mind the files can be quite large"

As I said before, you don't want to display very large files in your UI.  Even you if do all the reading in a background thread, when you switch to the main UI thread and actually attempt to render that information in the visual control it will take a long time and probably cause the form to lock up for a bit.

What is the user going to do with these very large files?  Why do they need so much information displayed at the same time?

I mentioned earlier that you may want to consider "paging" the data from the file so the user is only presented with a small subset of the data at a time and then they can click next/previous buttons to load the next "page" of data.
0
 
mondintatorAuthor Commented:
The textbox is trying to work like a text editor.  So for small files, what should I 'store' in the information in whilst the file is being read?
0
 
CodeCruiserCommented:
>So for small files, what should I 'store' in the information in whilst the file is being read?

String variable?
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Small files can go directly to the TextBox...no threading needed:

    TextBox1.Lines = System.IO.File.ReadAllLines(FileNameHere)
0
 
mondintatorAuthor Commented:
This advice has been fantastic, not only have you helped me get it working - you've easily explained the backgroundworker that I haven't been able to figure out reading manuals.
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 8
  • 7
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now