I made a file search program in visual studio on windows 10 using .net lang,
My problem starts from form1 with a "dim frm2 as form2 = new form2
" call,
after the new form being shown i start a while loop on form1 that feeds data into a listbox in form 2:
1)form1 call form2 and show it.
2)form1 start a while loop.
3)inside the while loop data being fed to listbox1 in frm2
Now everything works on windows 10, the while loop can run as much as it needs without any trouble, the window can loose focus and regain focus without showing any "Not Responding.." msgs or white\black screens..
But, when i take the software to my friend computer which is running windows 7, install all required frameworks and visual studio itself, run it from the .sln in debug mode, and do the same search on the same folder the results are:
1) the while loop runs smoothly as long as form 2 dont loose focus (something that doesnt happen on windows 10)
2) when i click anywhere on the screen the software loose focus what causes 1) to happen (black screen\white screen\not responding etc..)
3) if i wait the time needed for the loop and dont click anywhere else it keeps running smoohtly, updating a label like it should with the amount of files found.. and even finish the loop with 100% success (again unless i click somewhere)
Code Example:
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String) = New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push("...Directoy To Start The Search From...")
Do While (stack.Count > 0)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir) Then
Try
Try
Try
Dim directoryName As String
For Each directoryName In System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir, "*.*", System.IO.SearchOption.AllDirectories))
Catch ex5 As UnauthorizedAccessException
End Try
Catch ex2 As System.IO.PathTooLongException
End Try
Catch ex4 As System.IO.DirectoryNotFoundException
End Try
End If
Loop
End Sub
My conclusions was simple!
1) windows 7 dont support live ui (label) update from a while loop called from a button...
2) windows 7 could possibly support a new thread running the same loop
i think mabye if i run all the code in a thread mabye the ui will remain responsive
(by the way the UI is not responsive in windows 10 but i still see the label refresh and nothing crashes when form loose focus..)
so i know how to do that but i also know that if i do that a thread will not be able to update a listbox or a label in a form and refresh it..
so the thread will need to update an external file with the data and the form2 will need to read that data live from the file but will it make the same problems? i have no idea what to do.. can use some help and tips. THANK YOU!
I must menttion the fact that the loop is working on windows 10 without a responsive UI means i cant click on any button but i can still see the label refresh BUT on windows 7 everything works the same UNLESS i click somewhere, no matter where i click on windows the loop crashes
im using framework 4.6.2 developer
The reason your application is freezing is that you are doing all the work on the UI thread. Check out Async and Await. It uses threading in the background but makes it way easier to manage. An example here:
https://stephenhaunts.com/2014/10/14/using-async-and-await-to-update-the-ui-thread/
While I'm glad you found a solution, I advise against using
Application.DoEvents()
because it is bad practice.Please see this blog post: Keeping your UI Responsive and the Dangers of Application.DoEvents.
Simply put,
Application.DoEvents()
is a dirty workaround that makes your UI seem responsive because it forces the UI thread to handle all currently available window messages.WM_PAINT
is one of those messages which is why your window redraws.However this has some backsides to it... For instance:
If you were to close the form during this "background" process it would most likely throw an error.
Another backside is that if the
ScanButtonInForm1()
method is called by the click of a button you'd be able to click that button again (unless you setEnabled = False
) and starting the process once more, which brings us to yet another backside:The more
Application.DoEvents()
-loops you start the more you occupy the UI thread, which will cause your CPU usage to rise rather quickly. Since every loop is run in the same thread your processor cannot schedule the work over different cores nor threads, so your code will always run on one core, eating as much CPU as possible.The replacement is, of course, proper multithreading (or the Task Parallel Library, whichever you prefer). Regular multithreading actually isn't that hard to implement.
The basics
In order to create a new thread you only need to declare an instance of the
Thread
class and pass a delegate to the method you want the thread to run:...then you should set its
IsBackground
property toTrue
if you want it to close automatically when the program closes (otherwise it keeps the program open until the thread finishes).Then you just call
Start()
and you have a running background thread!Accessing the UI thread
The tricky part about multithreading is to marshal calls to the UI thread. A background thread generally cannot access elements (controls) on the UI thread because that might cause concurrency issues (two threads accessing the same control at the same time). Therefore you must marshal your calls to the UI by scheduling them for execution on the UI thread itself. That way you will no longer have the risk of concurrency because all UI related code is run on the UI thread.
To marhsal calls to the UI thread you use either of the
Control.Invoke()
orControl.BeginInvoke()
methods.BeginInvoke()
is the asynchronous version, which means it doesn't wait for the UI call to complete before it lets the background thread continue with its work.One should also make sure to check the
Control.InvokeRequired
property, which tells you if you already are on the UI thread (in which case invoking is extremely unnecessary) or not.The basic
InvokeRequired/Invoke
pattern looks like this:New UpdateTextBoxDelegate(AddressOf UpdateTextBox)
creates a new instance of theUpdateTextBoxDelegate
that points to ourUpdateTextBox
method (the method to invoke on the UI).However as of Visual Basic 2010 (10.0) and above you can use Lambda expressions which makes invoking much easier:
Now all you have to do is type
Sub()
and then continue typing code like if you were in a regular method:And that's how you marshal calls to the UI thread!
Making it simpler
To make it even more simple to invoke to the UI you can create an Extension method that does the invoking and
InvokeRequired
check for you.Place this in a separate code file:
Now you only need to call this single method when you want to access the UI, no additional
If-Then-Else
required:Returning objects/data from the UI with
InvokeIfRequired()
With my
InvokeIfRequired()
extension method you can also return objects or data from the UI thread in a simple manner. For instance if you want the width of a label:Example
The following code will increment a counter that tells you for how long the thread has run:
Hope this helps!