[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

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

BackgroundWorker Problem

Hi, I'm using VB.NET2010 (VS2010) WinForms. I have a method called "parseFile()" that parses a TXT file and writes to a Textbox Control as it performs a calculation on each row of dataTable, which I create within that method.  -- All that works well. (No problems so far...)

I then tried to add a BackgroundWorker, so that I can see the progress of my method as it parses the TXT file and so that I can add a progress bar to my Form, thus allowing me to let users know how far along the calculations are going.

However, once I added the BackgroundWorker, I get this error:

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

Note: ("tbxOutPut" is my Textbox Control)

The Texbox was created in the designer by adding a Textbox Control to the Form.


Below is the code that calls the BackgroundWorker. The line of code it stops on is below, which is the first time I access the Textbox Control once the method has begun.

>>  tbxOutPut.Clear() <--- I get the error here in my code

I know that I'm accessing one thread from within another. I know that is unsafe. However, I don't know how to do it in a "safe" manner - other than rewriting all my code and splitting the Textbox access to another method, which is not something trivial - nor something I'm willing to do, so I would really like to find a way to correct this error.

I tried also to look this up and found this post, but I'm still confused.

http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k(EHINVALIDOPERATION.WINFORMS.ILLEGALCROSSTHREADCALL);k(TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV4.0%22);k(DevLang-VB)&rd=true


Thanks,
Fulano
Private Sub btnParseFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnParseFile.Click
        BackgroundWorker1.RunWorkerAsync(btnParseFile)
    End Sub

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

Open in new window

0
Mr_Fulano
Asked:
Mr_Fulano
  • 5
  • 3
  • 3
  • +1
2 Solutions
 
Wayne Taylor (webtubbs)Commented:
You need to raise the ProgressChanged event by calling the ReportProgress method from within your 'parseFile' routine. You can then send messages or objects to the ProgressChanged event and use that information to populate the controls on your form and update the progressbar.

Wayne
0
 
Mr_FulanoAuthor Commented:
Hi webtubs, so what you mean is that each time I need to reach out to the Textbox control I need to use the ReportProgress event?

Fulano
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
That's correct.  You can pass a progress percentage number with the first parameter, and anything with the second parameter:

     BackgroundWorker1.ReportProgess(someNumberVariableHereForPercentage, ClassInstanceOrSimpleStringHere)

This will cause the ProgressChanged() event to fire where you can retrieve the values and update the GUI without causing the cross-thread error:

    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ProgressBar1.Value = e.ProgressPercentage
        tbxOutPut.AppendText(e.UserState & vbCrLf)
    End Sub
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
CodeCruiserCommented:
Make sure you set the WorkerReportsProgress property of the BGW to True as its false by default :-)


Also, given that you are parsing and processing a text file line by line, a progressbar may not be suitable. Incrementing a count of processed rows on the UI may be more suitable.
0
 
Mr_FulanoAuthor Commented:
I think CodeCruiser may be right...the BackgroundWorker may not be the best solution - for the way my code is written. THe BGW was an afterthought, so next time I'll have to plan for it and design my code accordingly.

Also from what Idle_Mind and Webtubbs are saying, the BGW is not going to work well with my code. I hit that Textbox way too many times (for each line I process) and using a BGW to come up for air on each line is not ideal.

OK...maybe back to the drawing board for me.

Fulano
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Approximately how many lines are there in a typical file that you are processing?
0
 
CodeCruiserCommented:
I did not say the BGW is not appropriate. I think a progressbar is not suitable in this case. You can use a label to update the number of rows that you have processed.

> I hit that Textbox way too many times (for each line I process) and using a BGW to come up for air on each line is not ideal.

I think it would be still better than a frozen UI.
0
 
Mr_FulanoAuthor Commented:
Hi Guys, to answer Idle_Mind's question...it can be anywhere from 15K lines to 450K lines. So, I need to show the user where he/she is in the process somehow.

If a BGW is still the best approach (progress bar or not) I will need to modify the code so that the textbox threading is in a method all by itself. I think I can do that, but it will take some working around what I already have.

Thanks,
Fulano
0
 
CodeCruiserCommented:
How long does it take to process those lines? Progressbar would not be suitable here as dividing 100 by 450K would yield a very little step value for the progressbar which would make it look like the progressbar is not changing at all. Processed row count is the way to go in my opinion.
0
 
Mr_FulanoAuthor Commented:
It takes maybe 7-8 minutes to process 15K lines and about 15-20 minutes to process 450K lines.

Would a percent value type of label not be about the same? I would think that the label would hang on each number for a short while before it worked its way up to the next value - maybe I'm not seeing this clearly.

Thanks,
Fulano
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Try something like the below.  The progress is based on the file pointer position in the underlying filestream and is only updated once per second via the Timer.  We still get to use a StreamReader with line by line access though:
Imports System.IO
Public Class Form1

    Private FS As FileStream = Nothing
    Private FileName As String = "C:\Users\Mike\Documents\Downloads\Patent_Absurdity_HD_3540kbit.ogv"

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Timer1.Interval = 1000
        Timer1.Enabled = False
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        If Not BackgroundWorker1.IsBusy Then
            Timer1.Start()
            Button1.Enabled = False
            BackgroundWorker1.RunWorkerAsync()
        End If
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim line As String
        FS = New System.IO.FileStream(FileName, IO.FileMode.Open, IO.FileAccess.Read)
        Using sr As New System.IO.StreamReader(FS)
            While sr.Peek <> -1
                line = sr.ReadLine

                ' ... process "line" ...

            End While
        End Using
    End Sub

    Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        If Not IsNothing(FS) Then
            Dim P As Decimal = CDec(FS.Position) / CDec(FS.Length)
            ProgressBar1.Value = ProgressBar1.Minimum + P * (ProgressBar1.Maximum - ProgressBar1.Minimum)
        End If
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        FS = Nothing
        Timer1.Stop()
        Button1.Enabled = True
        ProgressBar1.Value = ProgressBar1.Maximum

        MessageBox.Show("Done!")
    End Sub

End Class

Open in new window

0
 
Mr_FulanoAuthor Commented:
Sorry for the delay. I was working on getting to know the BGW a bit more before closing this out. I used most of Idle_Mind's approach, and what Webtubbs suggested about the ProgressChanged event. Together they worked out great.

Thank you all!!!
0

Featured Post

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

  • 5
  • 3
  • 3
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now