-
May 12th, 2015, 03:49 AM
#1
Thread Starter
Lively Member
[RESOLVED] Multithreaded class
So far I understand the basics of threads and how to use simple subs in threads. I now need to assign a function to a thread, and then queue another one.
My example is a computer search program that I'm working on right now. I know this is a lot of code, but It's easier to see what I'm trying to achieve this way.
This code belongs to a button which executes the search
Code:
Public var_locList As New List(Of String) 'I put paths into this
Dim var_temp As List(Of String) = task_MapoutDirectories(var_locList)
'Then display the var_temp of course... but that is obvious
Also, this is what I want to do, but It doesn't work this way:
Code:
Dim thread_dirSearch As System.Threading.Thread
thread_dirSearch = New System.Threading.Thread(AddressOf task_mapoutDirectories(var_locList))
thread_dirSearch.Start()
First function (first task) - Finds all dirs and sub dirs in the specified paths and returns the complete list
Code:
Public Function task_mapoutDirectories(ByVal var_newList As List(Of String)) As List(Of String)
Dim var_treeList As New List(Of String)
Dim var_tempList As New List(Of String)
Dim var_done As Boolean = False
Do
For Each var_location In var_newList
Try
var_tempList.AddRange(Directory.EnumerateDirectories(var_location, "*", SearchOption.TopDirectoryOnly).ToList)
Catch ex As Exception
End Try
Next
If var_tempList.Count = 0 Then
var_done = True
Else
var_newList.Clear()
var_newList.AddRange(var_tempList)
var_treeList.AddRange(var_tempList)
var_tempList.Clear()
End If
Loop Until var_done
Return var_treeList
End Function
Second function (second task) - Searches the directory list that I mapped out before for all files matching the search pattern
- This one is not optimized yet, I'm figuring out how to get rid of some of the 'for' statements
Code:
Public Function task_findFiles(ByVal var_locList As List(Of String), ByVal var_keyList As List(Of String), ByVal var_typeList As List(Of String)) As List(Of String)
Dim var_fileList As New List(Of String)
Dim var_matchList As New List(Of String)
If var_typeList.Count = 0 Then var_typeList.Add("*")
If var_keyList.Count = 0 Then var_keyList.Add("*")
For Each var_type In var_typeList
For Each var_key In var_keyList
For Each var_location In var_locList
Dim var_searchPattern As String = String.Concat("*", var_key, "*")
Try
var_fileList.AddRange(IO.Directory.GetFiles(var_location, var_searchPattern, SearchOption.TopDirectoryOnly))
Catch ex As Exception
End Try
For Each var_file In var_fileList
If var_file.Contains(var_type) Or var_type = "*" Then var_matchList.Add(var_file)
Next
var_fileList.Clear()
Next
Next
Next
Return var_fileList
End Function
Thanks for your answers in advance
-
May 12th, 2015, 10:14 AM
#2
Re: Multithreaded class
Rather than using raw threads, I think you would benefit greatly from taking a bit of time looking at Tasks. Tasks are lighter weight than threads, though a bit different. They are a relatively recent addition to the language, but I think that you would find them quite useful for what you are trying to do.
What you are doing doesn't look so wrong to me. There is no point in passing in an argument to task_mapoutDirectories, because the argument you are passing in is located at class scope, so it is accessible by the thread as well as the UI thread, and that seems fine. I would guess that is where your problem is coming from, though you didn't state why the code wasn't working. Still, I suspect that you'd find an easier solution using Tasks...once you get past the relatively minor learning curve.
My usual boring signature: Nothing
-
May 12th, 2015, 10:44 AM
#3
Thread Starter
Lively Member
Re: Multithreaded class
Originally Posted by Shaggy Hiker
Rather than using raw threads, I think you would benefit greatly from taking a bit of time looking at Tasks. Tasks are lighter weight than threads, though a bit different. They are a relatively recent addition to the language, but I think that you would find them quite useful for what you are trying to do.
What you are doing doesn't look so wrong to me. There is no point in passing in an argument to task_mapoutDirectories, because the argument you are passing in is located at class scope, so it is accessible by the thread as well as the UI thread, and that seems fine. I would guess that is where your problem is coming from, though you didn't state why the code wasn't working. Still, I suspect that you'd find an easier solution using Tasks...once you get past the relatively minor learning curve.
Ow, yeah, I didn't write what is wrong. So, when I'm trying to do this:
Code:
thread_dirSearch = New System.Threading.Thread(AddressOf task_mapoutDirectories(var_locList))
It doesn't work with the argument "(var_locList)" and that is a problem because it simplifies a large portion of the code (less variables, etc.). I don't know if Tasks will allow this, but I'll look into them. The error that It gives is "AddressOf' operand must be the name of a method (without parentheses)"
Also, there was another problem I was experiencing. In my function I had a piece of code that counted the number of directories and displaying it in real time but It was lagging the whole cycle, any way to use another thread or something to refresh it less often (it was just changing the text in a label)?
Last edited by FireDust; May 12th, 2015 at 10:49 AM.
-
May 12th, 2015, 10:56 AM
#4
Thread Starter
Lively Member
Re: Multithreaded class
Also, it's a function (task_mapoutDirectories), so there is a list of string that it returns, how do I assign it to a variable?
-
May 12th, 2015, 12:36 PM
#5
Re: Multithreaded class
Well, you don't. That would defeat the purpose of the threading. After all, if you were to write a line like this:
Dim someVar = SomeFunction()
someOtherVar = someVar
You would expect that SomeFunction would be executed and the return would be put into someVar. The next line would then do something with someVar. However, what you are doing is launching a different process that will do a bunch of work. You certainly don't want execution to stop on the thread.Start line until the process completes, as that would defeat the whole purpose of threading. Instead, you want execution to launch the process and move right on to the next line. If the next line were to use the return from the function, as in my example, it wouldn't work, because the process was still running. The time when that variable becomes available for the original process isn't known, so you can't know when the next line could be executed (unless you wrote some code to respond to some event from the thread, or joined the thread, or something like that). Therefore, you can't usefully return anything from a thread. What you'd have to do is put the information into a variable at class or form scope (such as your list), and have the thread signal the UI thread that it had completed. Tasks makes that a bit easier because you can chain actions together such that when the Task completes it has the next steps that are to be taken.
As for passing in the address of a method, that's all it is: An address to a function. That simply doesn't have enough information to allow for arguments to be passed, nor can it. Take a look at this link for examples of how you can pass arguments (except that it doesn't appear to be passing anything at all, but just relying on non-local variables):
https://msdn.microsoft.com/en-us/library/wkays279.aspx
My usual boring signature: Nothing
-
May 12th, 2015, 03:53 PM
#6
Thread Starter
Lively Member
Re: Multithreaded class
Originally Posted by Shaggy Hiker
Well, you don't. That would defeat the purpose of the threading.
Ok, I was kinda expecting that, but never saw anybody explain why or if there is any other option other than a public variable. Now, i found something like this:
Code:
'The declare and then run version probably does the same thing...
Await Task.Run(Sub()
task_MapoutDirectories()
End Sub)
of course it would be in a asynchronous sub. But this would stop the sub, and wait for the Task to finish, correct? If so, how do I run this and not lock up that sub?
Also, after I launch task_MapoutDirectories() I need a timer running to read the public variable updated by this sub. If I put the timer code into the sub, it just slows it down dramatically. The timer I want should run 10-15 times a second while the sub would run at a few thousand times a second. Any easier way than running another asynchronous sub?
If it's confusing I'll post some fictional code.
-
May 12th, 2015, 07:22 PM
#7
Re: Multithreaded class
Yeah, it's a bit confusing, but largely because I'm wondering what the timer is for. My assumption, which may be wrong, is that you are using the timer to poll for changes. That isn't normally ideal. It sounds like the sub will be adding things to a collection largely as fast as it can, whereas the foreground process will only be examining the collection periodically. If that's even close to being correct, you might want to look into the Queue(of String) rather than a list. My thinking would be that the sub would add items to the queue (perhaps only doing so if the item isn't already in the queue), then the foreground thread can just pop items off the queue if there are items to pop. That may or may not suit your needs.
I would start the Task and fire it off. Await will do just as you expect: Await the completion of the task, which doesn't seem like it does what you want, as it would freeze the foreground process. What you might consider is having the background process raise events on the UI process. In other words, the background process could alert the UI process of...well, whatever it makes sense to alert it of. For example, as the last act of the Task, it could raise an event on the UI thread. It sounds like the Task will be repeated so often that the UI wouldn't even want to know about EVERY time the Task completed, but something like that might be of interest. If you'd like to see an example of one way to do that, you can look for the UDP class I posted in the .NET CodeBank (just search for threads started by me in that forum, as I don't have all that many). One thing I was doing in that class was launching a thread that sat and listened for incoming messages. When one came in, it processed it, raised an event, and exited. Upon receiving the event, the handler would start a new thread to sit and listen again. That isn't quite what you are doing, but the pattern of doing something, raising an event when finished, and having the handler start the process over, seems similar.
My usual boring signature: Nothing
-
May 13th, 2015, 04:06 AM
#8
Thread Starter
Lively Member
Re: Multithreaded class
Close but not quite, right now, everytime the background thread finds a set of new files/directories it updates the UI thread (using Invoke) with their total count. If I had to guess, It would be around 1000 times a second.
Now, I want to have a timer that will periodically check the directory/file count in the list so it doesn't lag the UI thread so much. Example of my current 'update' code:
Code:
statusDisplay.obj_D_label.Text = String.Concat("Files Matching: ", var_matchList.Count)
I also had another problem, I went from tasks, to using good old threading because it has a easy way to Abort it
Code:
'I went from
Await Task.Run(Sub()
task_mapoutDirectories()
End Sub)
'to
Private thread_dirMapout As Threading.Thread
thread_dirMapout = New Threading.Thread(AddressOf task_MapoutDirectories)
thread_dirMapout.Name = "dirMapout Thread"
thread_dirMapout.Start()
thread_dirMapout.Join()
but now It just locks up the whole assembly and I can't find a reason why.. It actually executes the Start command. Thread actually starts running, but I tracked the problem to here:
Code:
Private Sub task_MapoutDirectories()
MsgBox("Running [1]")
If InvokeRequired Then
MsgBox("Running [2]")
Me.Invoke(New MethodInvoker(AddressOf task_MapoutDirectories))
MsgBox("Running [3]")
Else
MsgBox("Running [4]")
End If
End Sub
With the Await Task method I get 1; 2; 1; 4; 3; and then thread closes - Everything normal
With Thread method I get 1; 2; and then it locks up, and does absolutely nothing
Any ideas why? I would hate to read my own long posts, but it's kinda complicated and makes no sense... or I'm simply forgetting something basic :P
Last edited by FireDust; May 13th, 2015 at 04:09 AM.
-
May 16th, 2015, 01:06 PM
#9
Thread Starter
Lively Member
Re: Multithreaded class
I'm still having problems with "Me.Invoke(New MethodInvoker(AddressOf task_MapoutDirectories))" locking up my whole assembly. There is probably some other way than invoking, but I only know of turning off "Control.CheckForIllegalCrossThreadCalls", but I really don't want to do that. Any other ideas?
-
May 16th, 2015, 02:59 PM
#10
Re: Multithreaded class
Not followed this thread in its entirety, but a couple of observations about your post#8
Code:
thread_dirMapout = New Threading.Thread(AddressOf task_MapoutDirectories)
thread_dirMapout.Name = "dirMapout Thread"
thread_dirMapout.Start()
thread_dirMapout.Join()
The Join will block your UI thread until your thread_dirMapout thread finishes. But
Code:
Private Sub task_MapoutDirectories()
MsgBox("Running [1]")
If InvokeRequired Then
MsgBox("Running [2]")
Me.Invoke(New MethodInvoker(AddressOf task_MapoutDirectories))
MsgBox("Running [3]")
Else
MsgBox("Running [4]")
End If
End Sub
the Invoke attempts to marshal execution from the thread_dirMapout thread back onto the UI thread. The UI thread is still blocked, so nothing happens.
If the operation you are performing in your Sub task_MapoutDirectories() is a long one (and it sounds like it might be), should you really be marshaling its execution back onto the UI?
-
May 16th, 2015, 03:35 PM
#11
Thread Starter
Lively Member
Re: Multithreaded class
Even with the code I provided for Sub task_MapoutDirectories() it froze, and that sub has literally nothing to do.
I use Invoke because I want to communicate and change things (currently only a label) in a different thread, and that always gives me a CrossThreadCall error, and there has to be a different way than turning off Control.CheckForIllegalCrossThreadCalls.
-
May 16th, 2015, 06:09 PM
#12
Re: Multithreaded class
Turning off Control.CheckForIllegalCrossThreadCalls??? You should never use it ever.
-
May 16th, 2015, 06:17 PM
#13
Re: Multithreaded class
Originally Posted by FireDust
Even with the code I provided for Sub task_MapoutDirectories() it froze, and that sub has literally nothing to do.
My point was that your application freezes because Join is a blocking method that blocks the UI thread, and Invoke is a blocking method that blocks the non-UI thread. There's no where for the code execution to go.
You could try replacing the call to Invoke with a call to BeginInvoke, which is not a blocking method. That should allow the non-UI thread to continue and finish, which in turn would release the UI thread from the Join statement.
Originally Posted by FireDust
I use Invoke because I want to communicate and change things (currently only a label) in a different thread, and that always gives me a CrossThreadCall error, and there has to be a different way than turning off Control.CheckForIllegalCrossThreadCalls.
Indeed. NEVER call CheckForIllegalCrossThreadCalls unless you are upgrading old .NET 2.0 code which used a different mechanism when it came to cross thread calls to methods on Controls.
If you want to update a Label from a non-UI thread, supply a separate method to update the Label and call that method from the non-UI thread. You can then do the invoking within that method. What it looks like you are currently trying to do is to invoke the entire directory search method, not just the updating of the Label Control.
So for example, using the code in post#10, you would add a method similar to:
Code:
Private Sub UpdateLabel(progress As String)
If Label1.InvokeRequired Then
Label1.Invoke(Sub() UpdateLabel(progress))
Else
Label1.Text = progress
End If
End Sub
and change the non-UI code to:
Code:
Private Sub task_MapoutDirectories()
MsgBox("Running [1]")
UpdateLabel("some progress report")
MsgBox("Running [3]")
End If
End Sub
EDIT: sorry, I originally left the Invoke call in there by mistake
Last edited by Inferrd; May 16th, 2015 at 06:21 PM.
-
May 16th, 2015, 06:34 PM
#14
Thread Starter
Lively Member
Re: Multithreaded class
Great! BeginInvoke worked like a charm. Now I only need a easy way to do the next thing...
This code updates the label, and everything is good, but the non-UI Thread runs so quickly it manages to lag the UI Thread with the amount of updates. I need a easy way to periodically check and update the label. I can think of 2 ways, but both have a significant downside. One based on another asynchronous thread for refreshes with a delay, the other one counting up an integer variable and then reseting it while also refreshing the label.
-
May 16th, 2015, 06:40 PM
#15
Re: Multithreaded class
Them Two ways are terribl. Cn you show us the full code that "lags the" UI. i cant see how this is possible.
-
May 16th, 2015, 06:43 PM
#16
Thread Starter
Lively Member
Re: Multithreaded class
The program searches for every single directory and adds it to a list. Then it updates the form with how many it found.
Code:
Private Sub task_MapoutDirectories()
If InvokeRequired Then
Me.BeginInvoke(New MethodInvoker(AddressOf task_MapoutDirectories))
Else
Dim var_newList As New List(Of String)
var_newList.AddRange(var_locList) 'locList is where the first directories are saved, and then searched
Dim var_tempList As New List(Of String)
Dim var_done As Boolean = False
Do
For Each var_location In var_newList
Try 'Permision exceptions can pop up
var_tempList.AddRange(Directory.EnumerateDirectories(var_location, "*", SearchOption.TopDirectoryOnly).ToList)
Catch ex As Exception
End Try
Next
If var_tempList.Count = 0 Then
var_done = True
Else
var_newList.Clear()
var_newList.AddRange(var_tempList)
var_dirList.AddRange(var_tempList)
var_tempList.Clear()
End If
'Should be in a outside loop, too many refresh calls
statusWindow.obj_B_label.Text = String.Concat("Directories found: ", var_dirList.Count)
statusWindow.Refresh()
Loop Until var_done
End If
End Sub
This probably can be made a little more compact, but first I need to get it working properly
-
May 16th, 2015, 06:54 PM
#17
Re: Multithreaded class
You're still missing the point that because you're calling Me.BeginInvoke(New MethodInvoker(AddressOf task_MapoutDirectories)), everything after the Else is running on the UI thread, not on a different worker thread.
-
May 16th, 2015, 06:57 PM
#18
Thread Starter
Lively Member
Re: Multithreaded class
This is how I call the task_mapoutDirectories sub.
Code:
Private thread_dirMapout As Threading.Thread
thread_dirMapout = New Threading.Thread(AddressOf task_MapoutDirectories)
thread_dirMapout.Name = "dirMapout Thread"
thread_dirMapout.Start()
thread_dirMapout.Join()
It is still on the same thread? If so, then I really don't understand threads at all :P
-
May 16th, 2015, 07:07 PM
#19
Re: Multithreaded class
you do every thing on the UI thread.... it's clear you dont understand threads. Your code reads...
' do some thing
'check if we are on a secondary thread
'invoke to the ui
' do every thing on the ui
-
May 16th, 2015, 07:09 PM
#20
Thread Starter
Lively Member
Re: Multithreaded class
I'm guessing, that I do start a new thread, but because of the Invoke command it still forces it to run on the UI thread, correct? If so, I now understand why Inferrd creating a new sub, where only the label update was happening along with the Invoke. Gonna change that in the code. brb.
-
May 16th, 2015, 07:10 PM
#21
Re: Multithreaded class
Is the task_MapoutDirectories() Sub part of a Form, and can you move that form around the desktop while the code is running?
Edit: too slow
-
May 16th, 2015, 07:27 PM
#22
Thread Starter
Lively Member
Re: Multithreaded class
Looks like this is going to work, but because I never worked with Invoke, I'm kinda lost here. I want to use a similar sub, to what you mentioned earlier.
Code:
Private Sub UpdateLabel(progress As String)
If Label1.InvokeRequired Then
Label1.Invoke(Sub() UpdateLabel(progress))
Else
Label1.Text = progress
End If
End Sub
But I have multiple labels. Do I just stack them in the If command and invoke them all like so?
Code:
If Label1.InvokeRequired or Label2.InvokeRequired Then 'Also not sure if I should use "or" or "and"
Label1.Invoke(Sub() UpdateLabel(progress, progress1))
Label2.Invoke(Sub() UpdateLabel(progress, progress1))
Doesn't really seem right to stack them, but that's why I'm asking
-
May 16th, 2015, 08:07 PM
#23
Re: Multithreaded class
Originally Posted by FireDust
But I have multiple labels. Do I just stack them in the If command and invoke them all like so?
Code:
If Label1.InvokeRequired or Label2.InvokeRequired Then 'Also not sure if I should use "or" or "and"
Label1.Invoke(Sub() UpdateLabel(progress, progress1))
Label2.Invoke(Sub() UpdateLabel(progress, progress1))
Doesn't really seem right to stack them, but that's why I'm asking
No. The Label1.Invoke marshals code execution onto the thread that Label1 was created on. All the other Controls on the same Form will be on the same thread, so any code that accesses them can safely be placed between the Else and End If
Code:
Private Sub UpdateLabel(progress As String)
If Label1.InvokeRequired Then
Label1.Invoke(Sub() UpdateLabel(progress))
Else
Label1.Text = progress
Label2.Text = "some other string"
TextBox1.Text = "what ever"
' and so on
End If
End Sub
If you are updating multiple Controls, it might make it easier to follow if you change the sub to marshal execution onto the thread that the Parent Control (the Form in this case) is running on, along the lines of
Code:
Private Sub UpdateUI(progress As String)
If Me.InvokeRequired Then
Me.Invoke(Sub() UpdateUI(progress))
Else
Me.Label1.Text = progress
Me.Label2.Text = "some other string"
Me.TextBox1.Text = "what ever"
' and so on
End If
End Sub
Last edited by Inferrd; May 16th, 2015 at 08:13 PM.
Reason: Got there in the end
-
May 16th, 2015, 08:34 PM
#24
Re: Multithreaded class
One last thing: I'm fairly sure that the thread_dirMapout.Join() will be causing you problems by blocking the UI from updating until the worker thread finishes.
-
May 16th, 2015, 09:59 PM
#25
Re: Multithreaded class
Ya, Join will block. You only do that whaen you want to wait for a different process to complete before proceeding on the current process.
My usual boring signature: Nothing
-
May 17th, 2015, 03:44 AM
#26
Thread Starter
Lively Member
Re: Multithreaded class
That is correct, I want to wait for that task to finish, then change some variables and move on to another task.
Does it mean that the UI Thread will be blocked? I don't need to execute any code while the non-UI Thread is running, I just want it to be responding to draging the title bar, etc.
Edit: Just changed and tested the code, it does indeed lock up until both of my tasks are finished, but I think I know a way around this.
Last edited by FireDust; May 17th, 2015 at 03:48 AM.
-
May 17th, 2015, 10:25 AM
#27
Re: Multithreaded class
You might start a background thread that launches all the tasks. That background thread can then Join the tasks without penalty, but you certainly don't want the UI thread waiting for the background threads or you have defeated the whole purpose of threading. Basically, the UI thread must wait on nobody other than the user.
My usual boring signature: Nothing
-
May 18th, 2015, 04:29 AM
#28
Re: Multithreaded class
If you are dealing with Tasks, you don't need any background threads. You create a Task and attach continuations to them - a continuation is just a function/sub that will run when the Task completes and has a value, or when the task errors or is cancelled (in which cases, obviously it won't have a result). You can attach a continuation to multiple tasks that can be fired when all of them have completed, or when the first of them completes, depending on your scenario. This means you do not have to wait on any thread for the tasks, they will automatically run whatever follow on code they have been told about.
You can ask for these continuations to be run on the UI thread, which means you don't need to worry about invoking either.
Tasks can be created with a cancellation token, that you can keep hold of and use to signal to the task to cancel. The task has to check this and exit itself cleanly, but calling Abort on a background thread is pretty horrific practice and you should be using a signal to let the background thread clean up of its own accord anyway.
-
May 18th, 2015, 09:16 AM
#29
Thread Starter
Lively Member
Re: Multithreaded class
Alright, finally back to coding. Got it working with your help, though there is still a problem with the .Refresh() calls lagging the whole assembly. When I remove the whole refresh code, it becomes smooth and fast.
Main non-UI Thread:
Code:
Private Sub task_MapoutDirectories()
Dim var_newList As New List(Of String) : var_newList.AddRange(var_locList)
Dim var_tempList As New List(Of String)
Dim var_done As Boolean = False
Do
For Each var_location In var_newList
Try 'Permision exceptions can pop up
var_tempList.AddRange(Directory.EnumerateDirectories(var_location, "*", SearchOption.TopDirectoryOnly).ToList)
Catch ex As Exception
End Try
Next
If var_tempList.Count = 0 Then
var_done = True
Else
var_newList.Clear()
var_newList.AddRange(var_tempList)
var_dirList.AddRange(var_tempList)
var_tempList.Clear()
End If
sub_refreshLabel(String.Concat("Directories found: ", var_dirList.Count), statusWindow.obj_C_label.Text, statusWindow.obj_D_label.Text, statusWindow.obj_statPBar.Value)
Loop Until var_done
'Directory search complete
sub_editForms("Searching through directories", False, ProgressBarStyle.Continuous, 0, False)
var_finishTime.Add(String.Concat("Directory mapping complete: ", CStr(DateTime.UtcNow)))
'Start second task
thread_fileSearch = New Threading.Thread(AddressOf task_findFiles)
thread_fileSearch.Name = "fileSearch Thread"
thread_fileSearch.IsBackground = True
thread_fileSearch.Start()
End Sub
Sub that refreshes the label and a progress bar is sub_refreshLabel (obviously):
Code:
Private Sub sub_refreshLabel(ByVal var_B_text As String, ByVal var_C_text As String, ByVal var_D_text As String, var_pBarVal As Integer)
If Me.InvokeRequired Then
Me.BeginInvoke(Sub() sub_refreshLabel(var_B_text, var_C_text, var_D_text, var_pBarVal))
Else
statusWindow.obj_B_label.Text = var_B_text
statusWindow.obj_C_label.Text = var_C_text
statusWindow.obj_D_label.Text = var_D_text
statusWindow.obj_statPBar.Value = var_pBarVal
statusWindow.Refresh()
End If
End Sub
I'm going to work on it, but I'm open to any suggestions.
-
May 18th, 2015, 09:18 AM
#30
Thread Starter
Lively Member
Re: Multithreaded class
Originally Posted by Evil_Giraffe
If you are dealing with Tasks, you don't need any background threads. You create a Task and attach continuations to them....
Can you post just a few lines of code so I can see what you mean? Thanks
-
May 18th, 2015, 11:12 PM
#31
Re: Multithreaded class
Originally Posted by FireDust
When I remove the whole refresh code, it becomes smooth and fast.
Then don't use Refresh. Why would you need to do that anyway ?
-
May 21st, 2015, 05:28 PM
#32
Thread Starter
Lively Member
Re: Multithreaded class
Because I'm exceptionally tired and busy (you can hear that in the video, I'm reeaally tired ), I decided to make a quick video about my problem. I think I explained everything, if not, please ask.
Also, sorry that I nearly let the thread die
-
May 22nd, 2015, 06:02 AM
#33
Re: Multithreaded class
Hey, FireDust. Hope you're more awake now, you'll need to be......
I was also going to question why you are using Refresh, but Niya beat me to it.
From what you said in the video, it's to stop your status window from lagging; i.e., you can't drag it about the desktop, and the labels on it aren't updating when you feel they should be.
This is going to be due to your UI thread being choked, which you might consider strange given that the whole point of your using multithreading is to prevent this.
Well, it's probably down to your (mis)use of BeginInvoke. Yes, I know it was me who suggested its use, but when I suggested it, I was pointing out that your original complaint was caused by both your worker and UI threads being blocked at the same time: the UI thread by the call to Join, and the worker thread by the call to Invoke. So calling the non-blocking BeginInvoke instead would unjam your code (the alternative solution would have been not using Join in that situation).
The basic difference between Invoke and BeginInvoke is that a call to Invoke blocks the worker thread while the code invoked on the UI thread runs. This obviously will result in the worker thread taking longer to run as, every time you update the UI from the worker, the worker waits for the update code to finish running on the UI thread.
BeginInvoke, however, allows the worker thread to continue while the UI performs your update code at the same time.
BeginIvoke might sound like a more optimal solution. Well it is, unless you use BeginInvoke to marshal execution onto the UI thread faster than the UI can execute that code. If you call BeginInvoke again, before the previous code has finished running, the UI chokes on your update code, it has no time to pump its messages, it never gets a chance to handle the mouse drags or redraw itself.
Your use of Refresh "resolved" that situation by forcing an update of the whole Form, but it's a long way from being a good solution.
So I think your main problem is that you are trying to update the UI too often. How you solve that is up to you. If you continue using BeginInvoke, you have to code your worker thread in such a way that you allow enough time between calls. Or you could go back to using Invoke which will force the worker to wait until the UI is ready before it tries to update it again. That will slow the worker down slightly, but how much longer it takes is down to how efficient the UI update code is, and how many times it is called.
-
May 22nd, 2015, 08:39 AM
#34
Thread Starter
Lively Member
Re: Multithreaded class
Thanks for the explanation . Now, my first instinct is to use another thread and Thread.Sleep([num]) to update the labels. Do you have any other(better) ideas how to do it?
-
May 22nd, 2015, 09:59 AM
#35
Re: Multithreaded class
Originally Posted by FireDust
Do you have any other(better) ideas how to do it?
Ideas? Yes.
Good ideas? Probably not.
You need to limit the frequency that you update the UI with progress info. My first thought would be to use a Stop Watch to give a timed interval between updates. E.g. using the most recent code you have given (post#29) and typed free hand, so not tested:
Code:
Private Sub task_MapoutDirectories()
Dim var_newList As New List(Of String) : var_newList.AddRange(var_locList)
Dim var_tempList As New List(Of String)
Dim var_done As Boolean = False
Dim sw as New StopWatch
sw.Start
Do
For Each var_location In var_newList
Try 'Permision exceptions can pop up
var_tempList.AddRange(Directory.EnumerateDirectories(var_location, "*", SearchOption.TopDirectoryOnly).ToList)
Catch ex As Exception
End Try
Next
If var_tempList.Count = 0 Then
var_done = True
Else
var_newList.Clear()
var_newList.AddRange(var_tempList)
var_dirList.AddRange(var_tempList)
var_tempList.Clear()
End If
If sw.ElapsedMilliseconds> 250 Then
sub_refreshLabel(String.Concat("Directories found: ", var_dirList.Count), statusWindow.obj_C_label.Text, statusWindow.obj_D_label.Text, statusWindow.obj_statPBar.Value)
sw.Restart
End If
Loop Until var_done
'Directory search complete
sub_editForms("Searching through directories", False, ProgressBarStyle.Continuous, 0, False)
var_finishTime.Add(String.Concat("Directory mapping complete: ", CStr(DateTime.UtcNow)))
'Start second task
thread_fileSearch = New Threading.Thread(AddressOf task_findFiles)
thread_fileSearch.Name = "fileSearch Thread"
thread_fileSearch.IsBackground = True
thread_fileSearch.Start()
End Sub
Or use a loop counter and update after so many loops. Would need trial and error to set the count value, so probably less reliable at ensuring the UI doesn't choke.
Or use thread safe collections to store your retrieved Directories/Files, and a Forms Timer on the UI to update the UI with the Count properties of said collections. Sounds overcomplicated to me, but needs must where the Devil drives, and all that.
Or investigate Tasks as mentioned earlier. Not sure they would provide any easier a solution to this particular problem, but then I've never used them .
-
May 22nd, 2015, 10:09 AM
#36
Re: Multithreaded class
Just in passing, I don't think there's any need for this part of your code:
Code:
'Start second task
thread_fileSearch = New Threading.Thread(AddressOf task_findFiles)
thread_fileSearch.Name = "fileSearch Thread"
thread_fileSearch.IsBackground = True
thread_fileSearch.Start()
End Sub
You are calling that from the end of the current worker thread . It spins up a second worker thread just before the current worker thread terminates.
As you have it now, the two tasks run sequentially with no user intervention in regards to scheduling, so you might as well replace it all with a simple call to task_findFiles and continue on the current worker thread.
-
May 22nd, 2015, 10:53 AM
#37
Thread Starter
Lively Member
Re: Multithreaded class
Originally Posted by Inferrd
Just in passing, I don't think there's any need for this part of your code:
Code:
'Start second task
thread_fileSearch = New Threading.Thread(AddressOf task_findFiles)
thread_fileSearch.Name = "fileSearch Thread"
thread_fileSearch.IsBackground = True
thread_fileSearch.Start()
End Sub
You are calling that from the end of the current worker thread . It spins up a second worker thread just before the current worker thread terminates.
As you have it now, the two tasks run sequentially with no user intervention in regards to scheduling, so you might as well replace it all with a simple call to task_findFiles and continue on the current worker thread.
Oh yeah, good point
Thanks!
-
May 23rd, 2015, 09:47 AM
#38
Thread Starter
Lively Member
Re: Multithreaded class
Thank you all for your responses, I think I learned what I needed!
Marking as resolved.
Also, while re-doing some parts of the code, I scrapped the overcomplicated task_mapoutDirectories sub, and came up something I think is worth sharing and that nobody suggested.
Old code:
Code:
Private Sub task_MapoutDirectories()
Dim var_newList As New List(Of String) : var_newList.AddRange(var_locList)
Dim var_tempList As New List(Of String)
Dim var_done As Boolean = False
Do
For Each var_location In var_newList
Try 'Permision exceptions can pop up
var_tempList.AddRange(Directory.EnumerateDirectories(var_location, "*", SearchOption.TopDirectoryOnly).ToList)
Catch ex As Exception
End Try
Next
If var_tempList.Count = 0 Then
var_done = True
Else
var_newList.Clear()
var_newList.AddRange(var_tempList)
var_dirList.AddRange(var_tempList)
var_tempList.Clear()
End If
Loop Until var_done
End Sub
New code
Code:
Private Sub sub_mapout()
Dim var_index As Integer = 0
While var_dirList.Count <> var_index
Try
var_dirList.AddRange(Directory.EnumerateDirectories(var_dirList.Item(var_index), "*", SearchOption.TopDirectoryOnly).ToList) : var_index += 1
Catch ex As Exception : End Try
End While
End Sub
I thought it was too simple, but It works perfectly so far
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|