I've run into a little issue with a simple file copying application I'm writing in Visual Basic 2005. I have a main thread which looks after the GUI, and for the file scanning/copying I've created a separate thread which I create like this:
trd_copy = New Thread(AddressOf CopyTask)
trd_copy.IsBackground = True
trd_copy.Start()
This works fine for the scanning phase of the operation, and I can use the buttons in the GUI just fine. The problem is that when CopyTask gets to the file copying phase (using File.Copy), the main thread appears to lock up, and the GUI with it, meaning that the button I have there for aborting the copy operation is useless. When the copying is done, all returns to normal, and during copying the sub thread can update the status bar on the main form.
I'm sure I'm missing something simple, but I can't for the life of me see what it is.
Thanks very much!
Edit: Adding the code for CopyTask():
Private Sub CopyTask()
Control.CheckForIllegalCrossThreadCalls = False
If check_scanfirst.Checked Then
status1.Text = "Scanning..."
bytestocopy = 0
scandir(src)
filesscanned = True
MsgBox("Scanning completed")
End If
If check_delete.Checked Then
' Do a clean of the destination, removing any files that don't exist in the source dir
End If
If filesscanned Then
ProgressBar1.Visible = True
ProgressBar1.Minimum = 0
ProgressBar1.Maximum = 100
ProgressBar1.Refresh()
End If
checkdir(src)
MsgBox("Copying completed")
If filesfailed > 0 Then
MsgBox("Warning: " + Str(filesfailed) + " files were not copied successfully.")
End If
guistop()
End Sub
From what i can tell all calls to the System are also GUI Thread based no matter if you want them or not. And Control.CheckForIllegalCrossThreadCalls = False is very bad, you always want to write your thread and ( if beginner ) run code and everytime it breaks out to code to write a delegate and an invoke function for that part of your thread ( taking up the most minimal time ) one can in the Gui(Main) thread.
Here is an example with full sourcecode
http://www.codeproject.com/Articles/15104/Multithreading-with-VB-NET-A-beginner-s-choice
in your thread
Control.CheckForIllegalCrossThreadCalls = False -- this needs removed
If check_scanfirst.Checked Then -- this needs a delegate and invoke method
status1.Text = "Scanning..." -- this needs a delegate and invoke method
bytestocopy = 0
scandir(src) -- if scandir(has calls to gui )this needs a delegate and invoke method but inside of Scandir()
filesscanned = True
MsgBox("Scanning completed") -- this needs a delegate and invoke method ( keep in mind thread will keep running even if mesgbox not clicked )
End If
If check_delete.Checked Then -- this needs a delegate and invoke method
' Do a clean of the destination, removing any files that don't exist in the source dir
End If
If filesscanned Then
ProgressBar1.Visible = True -- this needs a delegate and invoke method
ProgressBar1.Minimum = 0 -- this needs a delegate and invoke method
ProgressBar1.Maximum = 100 -- this needs a delegate and invoke method
ProgressBar1.Refresh() -- this needs a delegate and invoke method
End If
checkdir(src)
MsgBox("Copying completed") -- this needs a delegate and invoke method
If filesfailed > 0 Then
MsgBox("Warning: " + Str(filesfailed) + " files were not copied successfully.") -- this needs a delegate and invoke method
End If
guistop() -- this needs a delegate and invoke method if (guistop makes calls to gui )
so if you can't tell your making lots of calls to the gui... heres how i would write it to make it really simple
how i would write your code ( wrote this in word some code may need a tweak )
Private sub DoStuffBeforeCopy()
If check_scanfirst.Checked Then
status1.Text = "Scanning..."
bytestocopy = 0
trd_copy.ParameterizedStart(src) //start thread
end sub
CopyTask(byval src as *string*?)
scandir(src) – put the code here or make another thread ( src is better )
filesscanned = True
invoke-MsgBox("Scanning completed")
invoke If check_delete.Checked Then
If filesscanned Then
Invoke-ProgressBar1.Visible = True
Invoke-ProgressBar1.Minimum = 0
Invoke-ProgressBar1.Maximum = 100
Invoke-ProgressBar1.Refresh()
End If
checkdir(src) – put the code here or make another thread ( src is better )
invoke-MsgBox("Copying completed")
If filesfailed > 0 Then
Invoke-MsgBox("Warning: " + Str(filesfailed) + " files were not copied successfully.")
End If
guistop()– put the code here or make another thread ( src is better )
End sub
** DO THIS FOR ANY Delegat / invoke needed
for threads calls with parameters
trd_copy.ParameterizedStart(src)
Delegate Sub nameofDelegate(s As Integer)
Sub nameofDelegate+NameofSub(ByVal s As Integer)
If Form1.ProgressBar1.InvokeRequired Then
Dim d As New nameofDelegate (AddressOf nameofDelegate+NameofSub)
NameOfYourForm.Invoke(d, New Object() {s})
Else
If s = 1 Then
NameOfYourForm.ProgressBar1.Refresh() ** Or other Gui Functions
Else
End If
End If
End Sub
For Thread calls without parametrs
trd_copy.Start()
Delegate Sub nameofDelegate()
Sub nameofDelegate+NameofSub()
If Form1.ProgressBar1.InvokeRequired Then
Dim d As New nameofDelegate (AddressOf nameofDelegate+NameofSub)
NameOfYourForm.Invoke(d, New Object())
Else
NameOfYourForm.ProgressBar1.Refresh() ** Or other Gui Functions
End If
End Sub
Related
I have written a server program that does a lot of jobs in threads simultaneously.
In those threads, I have to update a ListView with status information, but as it is right now using invoke, the thread waits for the UI to finish updating the ListView.
Any good advice to how I can send the status to the ListView and continue the thread while ListView finish updating?
Here's my code...
Public Delegate Sub InfoDelegate(status As String)
Public Sub Info(status As String)
If Me.InvokeRequired Then
Dim d As New InfoDelegate(AddressOf Info)
Me.Invoke(d, status)
Else
Dim item As New ListViewItem With {
.Text = status}
With lv
.BeginUpdate()
.Items.Insert(0, item)
If .Items.Count > 500 Then
For i As Integer = Me.lv.Items.Count - 1 To 500 Step -1
Me.lv.Items.RemoveAt(i)
Next
End If
.EndUpdate()
End With
End If
End Sub
You can call Control.BeginInvoke() to invoke the method asynchronously. However that call needs to be followed by a EndInvoke() call, or else you will get memory and/or thread leaks.
In the .NET Framework versions 4.0 and up you can utilize lambda expressions to pass the IAsyncResult returned from the BeginInvoke call to the lambda expression itself. Thus, you can call EndInvoke without having it block since by the time that it is called the asynchronous operation is already finished.
Here's an example:
Dim iar As IAsyncResult = _
Me.BeginInvoke(Sub()
Info("Status here") 'Calling your Info() method.
Me.EndInvoke(iar)
End Sub)
Getting cross thread error when executing tcViewer.TabPages.Add(t) statement.
Code is as below.
Private Function fff(t As TabPage)
tcViewer.TabPages.Add(t) 'giving cross thread error
End Function
Function WebBrowserThread()
Dim t As TabPage = New TabPage((k + 1).ToString())
t.Name = k.ToString()
tcViewer.Invoke(fff(t))
End Function
Please guide.
I think you should move the creation of the new TabPage onto the UI thread as well:
Private Function fff(k as Integer)
Dim t As TabPage = New TabPage((k + 1).ToString())
t.Name = k.ToString()
tcViewer.TabPages.Add(t)
End Function
Function WebBrowserThread()
tcViewer.Invoke(fff(k))
End Function
When you construct the TabPage, you eventually reach this call stack:
System.Windows.Forms.dll!System.Windows.Forms.Control.CreateHandle()
System.Windows.Forms.dll!System.Windows.Forms.Application.MarshalingControl.MarshalingControl()
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.MarshalingControl.get()
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.WindowsFormsSynchronizationContext()
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.InstallIfNeeded()
System.Windows.Forms.dll!System.Windows.Forms.Control.Control(bool autoInstallSyncContext)
System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.ScrollableControl()
System.Windows.Forms.dll!System.Windows.Forms.Panel.Panel()
System.Windows.Forms.dll!System.Windows.Forms.TabPage.TabPage()
System.Windows.Forms.dll!System.Windows.Forms.TabPage.TabPage(string text)
At this point, the Handle is being created, and if you're doing that on the wrong thread, everything else is going to start going wrong (because the thread that the control was created on isn't going to run a message pump)
i don't know what invoking error you get but i suggest disabling cross-thread checking by adding this in the constructor or loaded event (very helpful when dealing with APIs)
Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Check this http://tech.xster.net/tips/invoke-ui-changes-across-threads-on-vb-net/
in wpf such problems are easy to fix because you have a single thread for all the controls (Dispatcher.Invoke)
Update
dealing with the UI controls have to be on the UI thread
Me.Invoke(sub()
Dim t As TabPage = New TabPage((k + 1).ToString())
t.Name = k.ToString()
fff(t)
End Sub)
Me.Invoke(sub()
tcViewer.TabPages.Add(t)
End Sub)
I'm running the below function and it's a bit hit and miss, sometimes it loops back through and runs Call ShowProgressDialog(Nothing, Nothing) other times it just does the If Me.Dispatcher.Thread Is System.Threading.Thread.CurrentThread Then and jumps straight to the Else...
Private Function CloseDownTheApplication() As Task
'-----------------------------------------------------------------------------------------------------------
' Test whether the thread that owns the control of interest is the current thread...
' If it's not, you invoke a delegate referring to the same method via the control of interest.
'-----------------------------------------------------------------------------------------------------------
If Me.Dispatcher.Thread Is System.Threading.Thread.CurrentThread Then
Call ShowProgressDialog(Nothing, Nothing)
Else
Me.Dispatcher.Invoke(New Action(AddressOf CloseDownTheApplication))
End If
End Function
Some additional info - It's initialised from a Background Worker which is initiated from worker.RunWorkerAsync() which works absolutely fine, but it is the reason I need to cross over the threads to run my code.
The ShowProgressDialog runs this code block if it matters:
Private Async Sub ShowProgressDialog(sender As Object, e As RoutedEventArgs)
Dim controller = Await Me.ShowProgressAsync("Please wait...", "We are baking some cupcakes!")
Await Task.Delay(5000)
controller.SetCancelable(False)
Dim i As Double = 0.0
While i < 6.0
Dim val As Double = (i / 100.0) * 20.0
controller.SetProgress(val)
controller.SetMessage("Baking cupcake: " & i & "...")
If controller.IsCanceled Then
Exit While
End If
'canceled progressdialog auto closes.
i += 1.0
Await Task.Delay(2000)
End While
Await controller.CloseAsync()
Me.Close()
End Sub
I've googled it to death and all I can find really is a different way to check using Dispatcher.CheckAccess but this makes no difference. Also I'm on VS Express For Windows Desktop and using VB.Net.
I have written in vb.net for progress bar. I am thinking of there is a better way than this.
Here is what my code is :
Private Function ImportDataFiles(ByRef pobjDataLoadDTO As DataLoadDTO) As Boolean
Try
lblStatus.Visible = True
lblStatus.Text = ""
myProgressBar.Visible = True
myProgressBar.Value = 0
For Each drRow As ImportData.TRow In pobjDataLoadDTO.FileInfo.Select("categ_code = 'abc'")
If pobjDataLoadDTO.FileTimes.ContainsKey(drRow.KEY_CODE) AndAlso _
pobjDataLoadDTO.FileTimes(drRow.KEY_CODE) > pobjDataLoadDTO.UploadTimes(drRow.KEY_CODE) Then
pobjDataLoadDTO.DestinationTablename = drRow.KEY_CODE
If mobjDataLoadBO.ImportDataFiles(pobjDataLoadDTO) Then
drRow.DATA_TXT = mobjCommonBO.ONow.ToString
End If
End If
lblStatus.Text = drRow.KEY_CODE.Trim & "is loading...."
lblStatus.Refresh()
myProgressBar.PerformStep()
lblStatus.Refresh()
Next
Return True
Catch ex As Exception
Return False
End Try
End Function
Right now It is working, But I want to use more efficient way, like using Backgroundworkerprocess...etc., Any Ideas on this one?
Since your function runs in main thread, I assume your application is freezing and not very smooth while upload in progress.
1 - Drop Backgroundworker control on the form
2 - set "reportProgress" property of the worker to "True"
3 - Move your loop code into "DoWork" event of the worker control. And call worker.RunWorkerAsync. You can pass needed arguments to it
4- the code that refreshes progress bar move into "ProgressChange" event of the worker. This is important as you can't call control from worker thread. and ProgressChange is running in the main thread. You can also delete "Refresh" method call. That will not be needed anymore. Every time you want to refresh the progress bar call "ReportProgress" method of the worker
5-Use "RunWorkerCompleted" worker event, to do your clean up, and hide your progress bar
Its also might be a good idea to check if worker is already working before initiating, like
If worker.IsBusy Then
If worker.CancellationPending = False Then
worker.CancelAsync()
End If
Exit Sub
End If
I have a weird exception that I can't seem to debug. For anyone about to suggest worker threads I am prohibited from using them at my work... I asked why not myself and received a vague answer and clear directions to avoid it... anyways...
I decided I wanted to have a progress bar on a separate form that is initialized and shown directly from my library class (independent of the main form and executing on a different thread). Even though this class initializes the form itself, I am using control.InvokeRequired and control.Invoke anyway so I can reuse the form elsewhere.
The following is for initializing the progress bar:
Public Sub InitializePB(ByVal _Maximum As Integer, ByVal _Step As Integer, ByVal _StartValue As Integer, ByVal _Style As Windows.Forms.ProgressBarStyle)
If Me.InvokeRequired Then
'Different thread - invoke delegate
pb_Progress.Invoke(Sub() InitializePB(_Maximum, _Step, _StartValue, _Style))
Else
'Same thread - set values
pb_Progress.Minimum = 1
pb_Progress.Maximum = _Maximum
pb_Progress.Step = _Step
pb_Progress.Value = _StartValue
pb_Progress.Style = _Style
Me.Refresh()
End If
End Sub
Now for the step function:
Public Sub PerformStepPB()
If Me.InvokeRequired Then
'Different thread - invoke delegate
pb_Progress.Invoke(Sub() PerformStepPB())
Else
'Same thread - perform step
pb_Progress.PerformStep()
Me.Refresh()
End If
End Sub
Now the following is test code from where I launch form containing the progress bar and then send step calls:
pbForm = New frmProgress(_OwnerDesktopLoc)
pbForm.Show()
pbForm.InitializePB(100, 1, 1, Windows.Forms.ProgressBarStyle.Blocks)
Dim msElapsedVals As Integer = 0
While msElapsedVals <= 100
pbForm.PerformStepPB()
Thread.Sleep(100)
msElapsedVals += 1
End While
pbForm.Close()
pbForm.Dispose()
While Not pbForm.IsDisposed
Thread.Sleep(100)
End While
Exit Sub
So far so good... you will notice that I call the Form.Refresh method for the progress bar form every time the step method is called. I ended up doing this because when the form launched it would crash after several progress bar steps. At this point in time the form does show without crashing (because of the refresh) except that if I click the form it crashes (and no exception is caught by Visual Studio). The weird thing is that the code from the library class for updating the progress bar continues without any problems as if the problem is simply with the Win32 window. Here is the Windows message associated with the crash:
Description:
A problem caused this program to stop interacting with Windows.
Problem signature:
Problem Event Name: AppHangB1
Application Name: DL_RDS_Sort.vshost.exe
I tried launching the form from the main form thread but with the same results. I have no clue what's going on... any help would be greatly appreciated =D
Update on exactly how things are being called:
From MainForm a thread is started with the address of a subroutine defined within a library class :
Private Sub but_Sort_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles but_Sort.Click
Me.Sorter.OwnerDesktopLoc = New Drawing.Point(Me.DesktopLocation.X + CInt(Me.DesktopBounds.Width / 6), Me.DesktopLocation.Y + CInt(Me.DesktopBounds.Height / 2))
'Start sorting thread
Me.sortThread = New Threading.Thread(AddressOf Me.Sorter.Sort)
Me.sortThread.Start()
Then in this thread's routine I am testing the initialization of the form and calling the methods (shown earlier) I wrote for updating the performance bar properties:
Public Sub Sort()
Try
IIf(_OwnerDesktopLoc = Nothing, useProgressBar = False, useProgressBar = True)
'TESTING//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
pbForm = New frmProgress(_OwnerDesktopLoc)
pbForm.Show()
pbForm.InitializePB(100, 1, 1, Windows.Forms.ProgressBarStyle.Blocks)
Dim msElapsedVals As Integer = 0
While msElapsedVals <= 100
pbForm.PerformStepPB()
Thread.Sleep(100)
msElapsedVals += 1
End While
pbForm.Close()
pbForm.Dispose()
While Not pbForm.IsDisposed
Thread.Sleep(100)
End While
Exit Sub
'TESTING//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Note that there is a lot more code following the testing area but it never gets called and does not play a part in this issue because I exit the subroutine. I normally use ShowDialog but this halts the execution which is not what I want.
Some things worth mentioning:
-the frmProgress has the ControlBox property set to false.
-the frmProgress has the FormBorderStyle set to FixedDialog
Also: When I use the Show() method, the Windows "busy circle" cursor appears when I hover my mouse over the shown form. ShowDialog() does not have this behaviour... It's as if the form is waiting for something... Hope this clarifies my problem.