views:

264

answers:

4

Hello,

I need to write a VB.Net 2008 applet to go through all the fixed-drives looking for some files. If I put the code in ButtonClick(), the UI freezes until the code is done:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    'TODO Find way to avoid freezing UI while scanning fixed drives

    Dim drive As DriveInfo
    Dim filelist As Collections.ObjectModel.ReadOnlyCollection(Of String)
    Dim filepath As String

    For Each drive In DriveInfo.GetDrives()
        If drive.DriveType = DriveType.Fixed Then
            filelist = My.Computer.FileSystem.GetFiles(drive.ToString, FileIO.SearchOption.SearchAllSubDirectories, "MyFiles.*")
            For Each filepath In filelist
                'Do stuff
            Next filepath
        End If
    Next drive
End Sub

Google returned information on a BackGroundWorker control: Is this the right/way to solve this issue? If not, what solution would you recommend, possibly with a really simple example?

FWIW, I read that Application.DoEvents() is a left-over from VBClassic and should be avoided.

Thank you.

+1  A: 

Put the process into a separate thread.... ...using the BackgroundWorker component.

Disable UI components that should not be usable while the process workd.

Finished - the UI will still be responsive.

TomTom
Nice suggestion about disabling UI components that could interfere with the background task.
JeffH
+3  A: 

The BackgroundWorker is a good way to solve your problem. Actually the documentation states this:

The BackgroundWorker class allows you to run an operation on a separate, dedicated thread. Time-consuming operations like downloads and database transactions can cause your user interface (UI) to seem as though it has stopped responding while they are running. When you want a responsive UI and you are faced with long delays associated with such operations, the BackgroundWorker class provides a convenient solution.

klausbyskov
Thanks everyone for the feedback. I'll try the BackgroundWorker control, and disable the button before launching the background thread.
OverTheRainbow
A: 

A. put up a PROGRESS BAR... update it and .REFRESH it ... If all you want is to show that your not dead.

B. DoEvents is evil sounds A LOT like "NEVER USE A GOTO..." pleeeeze pleeeze pleeeze there are times and circumstances where any language's syntax can be harmful AND helpful. Why jump through a million hoops just to essentially do "A" above?

<soapbox>

If you know that something takes a LONG TIME and you also know that no other operations can take place WHILE YOUR WAITING (i.e. it is essentially a serial process) than if you do ANYTHING like that and push it into "the background" then you'll be sprinkling "ITS_OK_TO_CONTINUE" booleans all through the rest of your code just waiting for the file process to end anyway.... whats the point of that? All you've done is complicate your code for the sake of... hmm... "good programming?" Not in my book.

Who cares if DoEvents is "left over" from the ICE AGE. Its EXACTLY the right thing in MANY circumstances. For example: The framework gives you ProgressBar.Refresh but you'll see that its not exactly "working" unless you post-pend a few DoEvents after it.

</soapbox>

C. A background task is just that -- background; and you generally use it to operate on NON-SERIAL tasks or at least asynchronous tasks that MAY or MAY NOT update the foreground at some point. But I'd argue that anytime a so-called background task HALTS the foreground then it is (almost) by definition --- a FOREGROUND task; regardless of HOW LONG it takes.

tobrien
If you're having drawing problems, invalidate() and update() to repaint immediately. DoEvents is an ugly hack that causes strange problems to appear.
aehiilrs
I think most of you have inferred way too much ... his problem is SIMPLE. DISABLING controls, creating delegates, using backgrounders; talk about overkill for what the OP indicated his problem was. And calling DoEvents a hack is actually kinda funny considering its history.
tobrien
Tell me, what exactly do you think DoEvents does?
aehiilrs
Forces VB main thread to "be nice" and allow other messaging threads to grab a few CPU cycles. In the case you're suggesting, its no better or worse than adding .invalidate and .repaint. They are doing the same thing. EXCEPT that there may be *other* threads that need attention other than the GUI thread(s.) Let me ask you what your experience has been which makes you say that it CAUSES strange problems?
tobrien
Quite a few, actually. First one that comes to mind is a C# 2.0 app where pressing enter down in a textbox would validate a field then spawn a new form. On larger forms that took a long time to load, though, we'd have a crash, a ding, or something else unexpected happen depending on what was going on when DoEvents() made KeyUp run before KeyDown finished.
aehiilrs
The real problem with using DoEvents for updating the screen is that it's using a sledgehammer where a screwdriver might be more appropriate. If you're going to block the UI anyway, why tell it to pump all of its messages when all you really want to do is tell it to draw?
aehiilrs
@aehiilrs -- I think doing ANYTHING that allows the MAIN THREAD to give up inside a callback (like KeyDown) is asking for trouble. Windows has been notorious for getting "asynchronous event messages" out of order. In the example you sighted I would agree that you have a particular situation where you want to provide a specific update (like re-drawing) from inside a callback is better handled by a very specific command (ie repaint) and DoEvents may lead to unexpected event handling. In the OP's case it seemed to me that there were no untoward circumstances where he needed to be concerned.
tobrien
He has a time-consuming action that (seems) to be REQUIRED before anything else can happen anyway... so that was the ProgressBar idea.
tobrien
I definitely agree with the progress bar, but think that disabling controls (to show that things aren't active) and using methods that mean repaint instead of 'do everything' (for clarity) is a better way to go about it. Code gets re-used and extended all the time, so there is something to be said for doing the minimum necessary and not a bit more.
aehiilrs
@aehiilrs -- yep, agreed. I tend to use a 'splash like screen' which contains the Progress Bar, and some text indicating what's going on and sits ON TOP of all other forms. In this way, it is (a) reusable and (b) prohibits all other activity. BTW, I got this idea from the MAC OS.
tobrien
A: 

The key is to seperate the UI code from the actual functionality code. The time-consuming functionality should run on a seperate thread. To achieve this, you can either:

  1. Create and start a Thread object by yourself
  2. Create a Delegate and use asynchronous invokation (using BeginInvoke).
  3. Create and start a BackgroundWorker.

As you mentioned, you should avoid Application.DoEvents(). A proper breakdown of the application's functionality will allow you to create an application which is designed to be responsive, rather than creating a non-responsive application with DoEvents "fixes" (which is costly, considered bad practice, and implies a bad design).

Since your method doesn't return a value and doesn't update the UI, the fastest solution might be creating a Delegate and using "fire and forget" asynchronous invokation:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Call New Action(AddressOf DrivesIteration).BeginInvoke(Nothing, Nothing)
End Sub

Private Sub DrivesIteration()
    Dim drive As DriveInfo
    Dim filelist As Collections.ObjectModel.ReadOnlyCollection(Of String)
    Dim filepath As String

    For Each drive In DriveInfo.GetDrives()
        If drive.DriveType = DriveType.Fixed Then
            filelist = My.Computer.FileSystem.GetFiles(drive.ToString, FileIO.SearchOption.SearchAllSubDirectories, "MyFiles.*")
            For Each filepath In filelist
                DoStuff(...)
            Next
        End If
    Next 
End Sub

BTW, For..Next blocks no longer have to end with "Next (something)", it is obsolete - VB now infers the (something) by itself, so there is no need to state it explicitly.

M.A. Hanin
Thanks for the answer. I prefer to add the variable in Next for easier reading.
OverTheRainbow