I am new to vb.net and was wondering why my function (Run_Process) takes priority over the timer?
The timer (which starts the progress bar) runs after the function call even though the timer is set before the function.
Timer2.Start()
ListBox1.Items.Add("Backing up the registry, Please wait as this may take some time...")
ListBox1.ForeColor = Color.SlateBlue
MsgBox(Run_Process("CMD.exe", "/C regedit.exe /e C:\MMG\Regbackup.Reg"))
Timer2.Stop()
ListBox1.Items.Clear()
The function itself runs a cmd command.
The timer code is
Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
ProgressBar1.Increment(1)
If ProgressBar1.Value = 100 And ListBox1.Items.Count() < 1 Then
Label1.Text = "Process complete with no obvious threats"
Button4.Enabled = False
Label1.ForeColor = Color.DarkGreen
Button1.Enabled = False
End If
Label3.Text = ProgressBar1.Value & (" %")
End Sub
I think you're mixing concepts.
You start the timer, and I suppose it's waiting Interval period prior to launch Tick Event. Meanwhile, your launching a CMD. Are you waiting for exit, or it's running in async mode?
Then, you stop the timer.... your progress bar could be on 10% or 4% ....
I mean: your cmd process and your timer, are not connected anyway.
How are launching CMD process?
Your timer it's growing from "1%" to "100%", in what interval? +1 every 1000 milisecs? 3000 milisecs? ... Your progress bar can be finished, and your CMD be still running.
If I were you, I would use a Thread or better a Task to perform this. But, you can consider to forget your Timer, and use a ProgressBar1.Style=Marquee", instead.
It looks like you use Run_Process from this question, the Call probably blocks until the process ends so your UI thread is not able to act on the timer event. I think you need to brush up on BackgroundWorker, Threadpool and likes.
In general you should use something like this pseudocode:
Start a BackgroundWorker that
Starts the Process
Reads the Output
Reports Progress (and ListBox Elements) via ReportProgress
Meanwhile your UI Thread
Handles the BackgroundWorkers ProgressChanged Event
Updates ListBox and ProgressBar
Exits when BackgroundWorker is done
Related
This question already has answers here:
How to create a Splash screen for VB.net program
(3 answers)
Closed 6 days ago.
My program took ~5-10 seconds to load and sometimes people using it would end up trying to open it again, which caused problems. I found a quick and easy way to make a "splashscreen" (in a sense) that pops up for a set amount of time immediately on execution. I found that the first order of events in a WinForm EXE loading was Handle Created. The answer is not a true splashscreen, but for a couple lines of code that can be easily added to a project, I think some people will like it.
The below code will show a MessageBox immediately on running the EXE and closes after 10 seconds.
Imports System.Threading
Private Sub Control1_HandleCreated(ByVal sender As Object, ByVal e As EventArgs) Handles Me.HandleCreated
Dim SplashScreen As New Thread(
Sub()
CreateObject("WScript.Shell").Popup("Program Initializing, Please Wait...",10, "Setup Tool")
End Sub)
SplashScreen.Start()
End Sub
I use Threading so that the MessageBox will not freeze the code and the program will open with or without the OK button being pressed. Doing a regular MessageBox.Show() will prevent any more code from running until the user clicks OK I have found.
The best way I have found to implement a splash screen which keeps the user informed via messages and/or a progress bar or animated wheel is the following.
Have a startup form eg Form1, and have it carry out all the tedious startup procedures which might cause any animated or progress bar graphic to get stalled in the event queue. Add a "BackgroundWorker" object to Form1 from the Toolbox and in my case I just named it BackgroundWorker1.
Before starting these routines, usually in the Form1_Load event, make a call to the BackgroundWorker.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
CallBackgroundWork()
StartRoutines() 'this is the heavy lifting routines to get the app working. Set the LoadingStatusflag (declared as a Global Variable"
to various values to tell the splashscreen to display different messages
Loadingstatus = 10 'triggers splashform to exit
CancelBackgroundWork()
End Sub
These are the other subs to support this
Sub CallBackgroundWork()
BackgroundWorker1.WorkerSupportsCancellation = True
BackgroundWorker1.WorkerReportsProgress = True
' call this method to start your asynchronous Task.
BackgroundWorker1.RunWorkerAsync()
End Sub
Sub CancelBackgroundWork()
' to cancel the task, just call the BackgroundWorker1.CancelAsync method.
BackgroundWorker1.CancelAsync()
End Sub
Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'' The asynchronous task we want to perform goes here
FormSplash.Show()
End Sub
My splashscreen has some label controls and pictureboxes and the FormSplash_Load event runs a stopwatch loop of 40ms and loads a series of images (24 in total) of a spinning wheel. This keeps running while the splashscreen is active. By setting the global variable Loadingstatus to various values within different part of the loading sequence in Form1 it can trigger the loop routine to display different messages example shown. An easy way to communicate between threads as you can't directly access objects between threads The wheel keeps spinning no matter how intensive the load routine in Form1 as it is running in another thread. I used a stopwatch loop as starting a timer doesn't work for me - maybe an event queue issue in splash form.
Private Sub FormSplash_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Show()
Me.Opacity = 1 'show this form
'now start a loop that gets ended by other thread through variable Loadingstatus flag
Dim ggtimer As New Stopwatch, lastvalue As Integer, FProgPosition as integer
ggtimer.Start()
lastvalue = ggtimer.ElapsedMilliseconds
nextimage:
FProgPosition += 1
If FProgPosition = 24 Then FProgPosition = 1 'has 24 frames in the animated image
Do 'loop for 40 ms
If ggtimer.ElapsedMilliseconds - lastvalue > 40 Then
lastvalue = ggtimer.ElapsedMilliseconds
Exit Do
End If
Loop
PictureBoxProgress1.Image = FProgIMG(FProgPosition)
PictureBoxProgress1.Refresh()
If Loadingstatus = 10 Then GoTo endsplash
If Loadingstatus = 1 Then
If CoreTempRunning = False Then
Me.LabelCoreTemp.Text = "CoreTemp is NOT Running"
Me.LabelCoreTemp.ForeColor = Color.White
'insert cross picturebox
PictureBoxCoreTemp.Image = My.Resources.ResourceManager.GetObject("Cross24x24")
loaderrorflag2 = True
Else
Me.LabelCoreTemp.Text = "CoreTemp is Running"
Me.LabelCoreTemp.ForeColor = Color.White
'insert tick picturebox
PictureBoxCoreTemp.Image = My.Resources.ResourceManager.GetObject("Tick24x24")
loaderrorflag2 = False
End If
Me.PictureBoxCoreTemp.Visible = True
Me.PictureBoxCoreTemp.Refresh()
Me.LabelCoreTemp.Left = Me.Width * 2 / 3 - Me.LabelCoreTemp.Width
Me.LabelCoreTemp.Refresh()
GoTo nextimage
endsplash:
ggtimer.Stop()
Me.Opacity = 0.01
Me.Hide()
End Sub
I am writing a program that will allow me to run a number of different hardware test routines.
The routines can be quite time consuming, lasting up to 30mins. During this time, I have to control a selection of test equipment to set up conditions and take measurements.
I was thinking that using a background worker to carry out the tasks would be ideal, and allow the UI to stay responsive. This worked well until one of the routines requires me to take measurements every 1.5seconds. I am using a system timer to trigger these events. The timer is created and started in the doWork sub of the background worker, however, I find that the delegate is running in the main(UI) thread and not in the background worker thread as I thought.
Am I doing something wrong? I have attached the main parts of a simplified program that has the same structure.
Private Sub getMeasurement()
' Runs in backgroundWorker thread
Me.TextBox2.Text = (System.DateTime.Now - startTime).TotalSeconds.ToString
startTime = System.DateTime.Now
Debug.Print("Thread name is " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
End Sub
Private Sub OnTimedEvent()
'Runs in own thread, Calls getMeasurement which runs in BackgroundWorker thread
Thread.CurrentThread.Name = "OTE"
Debug.Print("In OnTimedEvent, thread = " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
Dim ServiceTimerDelegate As New ServiceTimerDelegate(AddressOf getMeasurement)
Me.BeginInvoke(ServiceTimerDelegate)
End Sub
Private Sub backgroundWorker1_DoWork(ByVal sender As System.Object, _
ByVal e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Thread.CurrentThread.Name = "BW1"
mTimer = New Timers.Timer(1490) ' 14.9secs (allow for some latency)
AddHandler mTimer.Elapsed, New Timers.ElapsedEventHandler(AddressOf OnTimedEvent)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
Dim i As Integer
'main timer for measurements every 1.5secs (may change to take interval from UI)
Select Case e.Argument
Case "Sunday"
mTimer.Start()
Debug.Print("Thread in DoWork = " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
startTime = System.DateTime.Now
'main loop for temperature ramping
For i = 20 To 70
If (worker.CancellationPending = True) Then
e.Cancel = True
Else
Thread.Sleep(500)
worker.ReportProgress((i - 19) * (100 / 50))
i += 1
End If
Next
Case Else
e.Cancel = True
End Select
End Sub
Three things are confusing here, and make me think that the code you've posted above doesn't actually use the BackgroundWorker for its function.
The DoWork event always runs in a separate thread from the UI. That's the entire point of it. ReportProgress runs in the UI thread, but you're not making use of that.
You don't actually appear to be running the BackgroundWorker at all in the above code. Rather, you seem to be using a ServiceTimerDelegate - not 100% sure I know what that is. You've defined a BackgroundWorker within your DoWork handler (which makes no sense), but you never seem to be actually hooking a BackgroundWorker up to that handler nor calling RunWorkerAsync on it.
Even if you were running getMeasurement() in a BackgroundWorker, it should fail - you're altering the contents of a UI item, which should cause an exception due to InvalidCrossThreadAccess. If you need to alter a UI control, use the ReportProgress event, which occurs within the UI thread.
While I have some VBScript experience, this is my first attempt at creating a very simple VB.NET (Windows Forms Application) wrapper for a command line application. Please be kind!
I have a very simple GUI with two buttons that both do an action and I'd like to show a marquee progress bar until the action (read: the process) is complete (read: exits).
The 'save' button does this:
Dim SaveEXE As Process = Process.Start("save.exe", "/whatever /arguments")
From there I'm starting the marquee progress bar:
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I thought I could use SaveEXE.WaitForExit() but the Marquee starts, then stops in the middle until the process exits. Not very useful for those watching; they'll think it hung.
I thought maybe I could do something like this but that causes my VB.Net app to crash
Do
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
Loop Until SaveEXE.ExitCode = 0
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I'm not entirely sure what needs to be done, short of getting some formal training.
You can use the new Async/Await Feature of .NET 4.5 for this:
Public Class Form1
Private Async Sub RunProcess()
ProgressBar1.Visible = True
Dim p As Process = Process.Start("C:\test\test.exe")
Await Task.Run(Sub() p.WaitForExit())
ProgressBar1.Visible = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RunProcess()
End Sub
End Class
Note the Async keyword in the declaration of the RunProcess sub and the Await keyword.
You run the WaitForExit in another thread and by using Await the application basically stops at this line as long as the task takes to complete.
This however also keeps your GUI reponsive meanwhile. For the example I just show the progressbar (it is invisible before) and hide it once the task is complete.
This also avoids any Application.DoEvents hocus pocus.
During the startup of my app I am doing a long database upgrade.
Before that starts, I show a form that has a progressbar so that the user knows that something is going on and he should wait.
To not block the progressbar from redrawing, I do the database upgrade in a background worker.
The code looks like this:
frmMain_Load(...)
Dim wait As New frmWait
wait.Show()
Dim bw As New frmBWRebuildUserData
bw.Start()
Do While Not bw.Done
System.Threading.Thread.Sleep(100)
Loop
'Okay, db update was done, now continue and show the main app window
My frmBWRebuildUserData looks like this:
Public Class frmBWRebuildUserData
Private m_bDone As Boolean
Public ReadOnly Property Done() As Boolean
Get
Return m_bDone
End Get
End Property
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
modAppDB.RebuildUserDB()
End Sub
Public Sub Start()
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
m_bDone = True
End Sub
End Class
But after 60 seconds, VB.NET tells me that there were no messages since 60 seconds (I guess you know this error).
But since the background worker is intended for such purposes, I think I am doing something substantially wrong here, but I can't figure out what.
Also, my progressbar is not redrawing.
Can somebody help, please?
Thank you very much!
A couple of things.
There is no built in timeout of 60 seconds in the backgroundworker. So it should be something in your code.
Why do you use a backgroundWorker and then introduce in your code a sleep cycle? The backgroundworker should be used to free the user interface from waiting for the end of long operations.
The backgroundworker when asked to report its progress to a user interface element needs something like this (sorry is C#, but I think you can get the point)
backgroundworker.ProgressChanged += backgroundworker_ProgressChanged;
backgroundworker.WorkerReportsProgress = true;
in this scenario your modAppDB.RebuildUserDB() need to call
backgroundworker.ReportProgress(percentComplete);
for every step that you want to communicate to the progress bar and of course, you need in the form where the progressbar is displayed to intercept the event fired by the ReportProgress call
private void backgroundworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = (e.ProgressPercentage.ToString() + "%");
}
The Backgroundworker is mainly good for tasks where you have a loop inside DoWork, which allows you to do some UI feedback within the loop. If your DoWork just calls another command, it will wait for this command to finish and not do anything in this time. Other than that, using the BGW still allows for the main thread to handle its messages and not get blocked, so I presume it is still entirely right to use it here.
Apart from that, your backgroundworker1 is not declared, and as Steve pointed out, your Start()-Method needs at least this first line:
Addhandler Backgroundworker1.DoWork, AddressOf BackgroundWorker1_DoWork. This triggers the function when RunworkerAsync is called.
For a basic example of thread-communication (and fundamental problems connected with it) take a look at this question and answer:
Multithreading for a progressbar and code locations (vb.net)?
I don't know about the 60 seconds issue either.
I have this situation: a Form with a System.Timer in it (with AutoReset = False). The form has its main thread and the timer its own thread too (nothing new here).
When the user press a button I need to stop the timer, wait until the timer thread has stopped its execution and do something more.
On the other side, the timer updates an item at the form so BeginInvoke is used. The code looks like this:
Button Code:
Private Sub ButtonStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonStop.Click
SyncLock (m_stopLock)
m_stopProcessTimer = True
Threading.Monitor.Wait(m_stopLock)
End SyncLock
''#Do more things here after the timer has end its execution and is stopped
End Sub
Timer code:
Private Sub m_processTimer_Elapsed(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_processTimer.Elapsed
Dim auxDelegate As EventHandler
SyncLock (m_stopLock)
If Not m_stopProcessTimer Then
If Me.InvokeRequired Then
auxDelegate = New EventHandler(AddressOf m_processTimer_Elapsed)
Me.BeginInvoke(auxDelegate, New Object() {sender, e})
Else
DoFormStuf()
m_processTimer.Start()
End If
Else
Threading.Monitor.Pulse(m_stopLock)
End If
End SyncLock
End Sub
The point is that I wait the main thread to let the timer thread to end its work.
The problem is that this code deadlocks when the user clicks the button when the BeginInvoke is going to be called. How a simple thing like this one can be done? Looks like I cannot find a good solution to this problem :(
Don't use locks at all, just make sure to do everything on the UI thread, and you can guarantee that nothing will be corrupted. Remember that dispatcher items run on the UI thread, so you know that if you're doing everything either in a dispatcher item or an event handler, only one thing is executing at a time.
1) Perhaps a little more code would be helpful. Do you create a new thread and put the timer ON that thread?
2) Have you tried using ManualResetEvent (.WaitOne() and .Set() instead?)
3) In your event, if invoke is required, you are re-calling your event again. Confusing...
4) Are you supposed to wait until the other thread is done? Then Thread.Join()?