How to prevent UI from freezing during lengthy process? - vb.net

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.

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.

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.

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:
Create and start a Thread object by
yourself
Create a Delegate and use
asynchronous invokation (using
BeginInvoke).
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.

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.

Related

VB.Net Pausing Windows Shutdown or Logoff

I tried this to show some MsgBox when shutdown or logoff is detected.. like "You're logging off.. .
Public Class frmDetectEnd
Private Sub frmDetectEnd_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler Microsoft.Win32.SystemEvents.SessionEnding, AddressOf Handler_SessionEnding
End Sub
Public Sub Handler_SessionEnding(ByVal sender As Object, ByVal e As Microsoft.Win32.SessionEndingEventArgs)
If e.Reason = Microsoft.Win32.SessionEndReasons.Logoff Then
MessageBox.Show("User is logging off")
ElseIf e.Reason = Microsoft.Win32.SessionEndReasons.SystemShutdown Then
MessageBox.Show("System is shutting down")
End If
End Sub
End Class
This detection work's OK but I want to stop shutdown/logoff process if MsgBox is shown, because at this point the shutdown/logoff process is executed and it stops with Windows message "This program is preventing to log you off...".
However, I would like that after getting message "User is logging of/System is shutting down" the user can select command button to proces something and then Shutdown or LogOff can continue.
It's pretty much impossible to cancel a shutdown/logoff after it has started. At most you can get the "This program is preventing Windows from shutting down" message, like you said in your question.
A potential solution would be to create system-wide hooks for the InitiateShutdown, InitiateSystemShutdown and InitiateSystemShutdownEx functions (requires C++ or some other kind of low-level language). However, they would need to be coded carefully and tested thoroughly, and even if you managed to get them working most of the times, there's no guarantee that the OS isn't bypassing them via some other, even deeper functions (it contains a lot of both undocumented and name-mangled functions).
Source:
I researched this heavily about two years back in an attempt to make a program that would completely block system shutdown if the user chose to. However, as mentioned above, it has proven to be extremely difficult, if not impossible. None of my attempts were a success.
You could immediately call shutdown /a when a shutdown is detected. (/a stands for "abort").
That's a built-in command-line utility in Windows, so you'd call it the usual way of calling command-line utilities within a vb app. The trick is it has to happen fast enough.
Another more hacky way you could try would be to send a CTRL+ALT+DEL and then the Task Manager (and then hide it. Maybe you can do it all so fast the user would only see a quick flash). That usually aborts a shutdown when I've manually done it.

forcing only one instance of Asyc sub

I'd like to trigger a slow-running sub when particular emails arrive, but I don't want to block execution/freeze outlook.
I've written the sub as Public Async Sub... but part of the code would fall over if the sub was called twice in quick succession.
Is there any way to make a queue such that only one copy of my sub is running at any one time, while ensuring that it's still running in the background?
My solution at the moment is to have a public continue as Boolean with a waiting loop beforehand - but this is a bit on the ugly side!

SAPI Execution Priority

I use (SAPI) object in my application. However, whenever I executes its function, the voice runs but the application's performance (everything else) gets paused until the voice finishes! I wonder if that has something to do with the priority of this Speaking-Object. Can I somehow lower it until the rest of the code executes first? o_O
Private Function Lara(ByVal script As String) As Object
Lara = CreateObject("SAPI.spvoice")
Lara.Voice = Lara.GetVoices.Item(1)
Return Lara.speak(script)
End Function
Private Sub Test_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Logo.Visible = True
Logo.Image = ResizeImage(My.Resources.Spell)
Lara("Welcome")
End Sub
Can (Lara) say "welcome" AFTER loading the form and its Logo?
Thank you.
Thanks to Ms. Lesley Gushurst, the voice now runs according the code order (Application shows up, its logo, then the voice). The solution was to add a (com-reference) to the project named "Microsoft Speech Object Library", then importing it in the code.
Imports SpeechLib
Private Sub Test_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Logo.Visible = True
Logo.Image = My.Resources.Spell
Dim Lara = CreateObject("SAPI.spvoice")
Lara.Voice = Lara.GetVoices.Item(1)
Lara.speak("Welcome", SpeechVoiceSpeakFlags.SVSFlagsAsync) 'It's declared now!
End Sub
If you take a look at MSDN's documentation SpVoice Speak method, it states that
"When synchronous speech is used in an application, the application's
execution is blocked while the voice speaks, and the user is
effectively locked out. This may be acceptable for simple
applications, or those with no graphical user interface (GUI), but
when sophisticated user interaction is intended, asynchronous speaking
will generally be more appropriate."
So what you'll probably want to do is look into calling speak with the SVSFlagsAsync being set. As it is right now your execution is being blocked.
I'm thinking your code would look like:
Lara.speak(script, SpeechVoiceSpeakFlags.SVSFlagsAsync)
I wish mine was that easy. Mine SWITCHES voices on the fly according to the voices embedded in the text to be read. And I am fast finding out that not too many people are trying to do that.
My apps is a Visual Basic (VS2010) verbal editing tool for writers that speaks the chapter or story so the author can HEAR typos, incoherent sentences, etc. in male or female voices, depending on which character is speaking. Right now I have only 7 voices that are available although at one time I have had 12 and with 22 different characters assigned to them.
It worked fine on windows 7, 8 and 8.1 but started having big problems windows text.
When I finally get it working I will make the code available, maybe.

Can I avoid a possible hang by limiting the time a method is allowed to take?

I am using an external DLL (pdfsharp) to open (then manipulate) lots of PDF files. I use:
Dim inputDocument = Pdf.IO.PdfReader.Open(PDFPath, IO.PdfDocumentOpenMode.ReadOnly)
Problem is - it seems to hang on certain, rare files. I don't seem to get any timeout - it just hangs for hours on this line. We read thousands of files with this code, always on tiny files, so I was thinking that a quick workaround might be to somehow timeout if the this method takes more than a second or two. But I don't see a simple way to do this. I am hoping to avoid spinning up a worker thread.
Any thoughts on how I might limit this threads allowed execution time, or is there a better (but simple) way?
The Open() call should not hang. Never. If you provide us with a file that causes Open() to hang, we can investigate this.
Does your program run on a server? Do you use a DEBUG build of PDFsharp? Maybe it's just a simple Debug.Assert() that is triggered, but noone can answer it. Using a RELEASE build would solve this.
We ended up working around this problem by creating an AbortableBackgroundWorker. I am not sure whose code this ended up being - but we found it online and sharing it here. In the rare case where one of the PDF's hangs the PdfSharp Open() call, we abort the background worker.
Public Class AbortableBackgroundWorker
Inherits BackgroundWorker
Private workerThread As Thread
Protected Overrides Sub OnDoWork(e As DoWorkEventArgs)
workerThread = Thread.CurrentThread
Try
MyBase.OnDoWork(e)
Catch generatedExceptionName As ThreadAbortException
e.Cancel = True
'We must set Cancel property to true!
'Prevents ThreadAbortException propagation
Thread.ResetAbort()
End Try
End Sub
Public Sub Abort()
If workerThread IsNot Nothing Then
workerThread.Abort()
workerThread = Nothing
End If
End Sub
End Class

Synchronization in multi threading in .net

I have a question regarding the synchronization in multi threading.
I have a function called send which i am calling in a for loop for every data row.
This function creates threads to send emails to different people.
Public Sub send(ByVal Conn As Data.DataRow, ByVal job As Data.DataRow)
MyThread = New Thread(AddressOf Me.start)
MyThread.Start()
End Sub
But every thread created is writing to a log file at different points of time. So, there are very high chances that 2 threads write to the same file at the same time. So, do i need to use any kind of synchronization mechanisms here ? or is it fine without them ? If yes, can any one please tell me what to use and where to use.
To prevent multiple threads from writing at the same time, use SyncLock. For example, myLogFile is your log file:
SyncLock myLogFile
myLogFile.WriteLine("message");
End SyncLock
Several approaches:
Your thread can obtain a lock on your logger object prior to calling the loggers log methods. This makes it the callers responsibility to lock.
Optionally you can move the locking responsibility into the logger object by having it manage the locking. See Jim's answer.
If you aren't wrapping your logging functionality into a separate object then simply create an object in your code that you can lock on.
Additionally, I wouldn't spawn a thread per record unless you know you will always have a small number of records. Otherwise you'll create context switch thrashing and hurt perf. Look into the Thread.QueueUserWorkItem() static method.
You will have an access error if one thread is blocking the file because it is writing to it. You can handle the exception, have the current thread wait and then write to the file.
boolean AttemptingToWrite = true
while (AttemptingToWrite)
try
WriteToLog()
AttemptinToWrite = false
catch(ex as exception
system.threading.thread.sleep(100)
end try
end while