Is there a way to make a function wait for a specific external value before returning the result?
Here is a very rudimentary example:
Function DoSomething() As boolean
'' ... do something external that takes time and sets a Registry value when done
'' ...
Dim isTaskDone as Boolean
Do Until isTaskDone = True
isTaskDone = Registry.GetValue("ExternalValuePath", "valName", 0)
Loop
Return var
End Function
This actually works for my needs, but I was wondering maybe there is more elegant way of achieving this?
Also I am not sure whether this solution is OK to leave it like that (using Until to effectively stall the function).
I apologize for a rather vague question.
Update:
I guess I am looking for some sort of simple one-call-solution, where I can call one function, it does everything I need and returns a result only when everything is done. So I know it is safe to continue.
I have also tried this with Timers, but I don't think it is possible to contain everything in a one-call-solution when using Timers.
Better approach would be to create a registry watcher using WMI. Refer the link, its in C# should not be a problem to convert in VB.Net
Your method uses a busy wait loop which will lock up your UI (and waste your CPU cycles). It's possible a BackgroundWorker could be used for this but you could likely also accomplish it with a Timer. Not very elegant perhaps but it wouldn't freeze your interface.
So rather than make it a function, a Sub would do. Then add a Timer with default Interval of 100. In your DoSomething Sub, Enable the timer. And your timer code would be something like this
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
If Registry.GetValue("ExternalValuePath", "valName", 0) Then
Timer1.Enabled = False
'do whatever needs to be done next
End If
End Sub
Related
First of all, i don't have a good understanding about thread in vb, i tried to get info about it, but i'm still confused about how its work, addressof, the correct implementation and so on. I want to ask about how to create a new thread with parameter so i pass a value from the main thread.
I try to do a loop and each loop create a new thread and do ping. Below is my code:
For i = 10 To 50
Dim worker As New Thread(New ThreadStart(Function()
My.Computer.Network.Ping("192.168.1." & i)
Console.WriteLine("192.168.1." & i)
End Function))
worker.Start()
Next
I realize this is wrong because the result will loop and ping the last value of variable i. So i want to ask about the correct suggestion about it. I will really appreciate if you add a simple explanation for me just to get a better understanding about thread.
Thank you in advance
Your code is almost correct. However, since you are using the same variable for all your created threads. So when your thread starts it more or less contains a pointer to i rather than the value of i. Which means that as your loop progress the value of i change. So when your threads actually start working the loop has completed and the value of i has changed to 51.
To resolve this you have to create a new integer inside the loop. So that the supplied object is unique for every loop. Like so:
Dim x as Integer = i
Then supply x instead of i to the Thread and you are all set.
Here is some reading on the topic:
https://devblogs.microsoft.com/vbteam/closures-in-vb-part-5-looping/
You may also want to try async/await as a better alternative.
Example
Make sure you make the caller async method by using the async keyword:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For i As Integer = 1 To 5
Dim ip As String = $"192.168.1.{i}"
Await Task.Run(Sub() Pinger(ip))
Next
End Sub
Private Sub Pinger(ip As String)
If My.Computer.Network.Ping(ip) Then
Console.WriteLine($"{ip} is alive.")
Else
Console.WriteLine($"{ip} is dead.")
End If
End Sub
The output:
192.168.1.1 is alive.
192.168.1.2 is dead.
192.168.1.3 is dead.
192.168.1.4 is dead.
192.168.1.5 is alive.
Why do you get an ordered sequence? I quote:
The method usually includes at least one await expression, which marks
a point where the method can't continue until the awaited asynchronous
operation is complete.
Please refer to the given link above for more useful details.
So essentially I have wired up three text boxes to do a smart filter and want to let a user do a multi filter. The only problem was that it was firing too frequently and I want to have it fire after a delay. The event for 'TextChanged' is wired up to basically run and I have a simplified example of what I want:
I have a simple Winforms UI with two text boxes: "txtWait" and "txtTest". In the front end code the properties are default and the text are:
txtWait.Text = 1000
txtTest.Text = "Test Text I have here to look at"
A way to test this is to just hit the backspace a few times and wait. I would want only the last text to show once. I just got this part to work but the resetting it not occurring as I would expect. I would expect a person could hit backspace, backspace, (only a half a second had passed), backspace(clock resets and new wait begins).
And my code behind is:
Public Class DelayBeforeAction
Private _loaded As Boolean = False
Private _sw As Stopwatch = New Stopwatch()
Public Sub New()
InitializeComponent()
_loaded = True
End Sub
Private Sub txtTest_TextChanged(sender As Object, e As EventArgs) Handles txtTest.TextChanged
If _loaded Then
_sw.Start()
DelayExecute(Sub() If _sw.ElapsedMilliseconds > CInt(txtWait.Text) Then _sw.Reset() : MessageBox.Show(txtTest.Text) Else _sw.Reset(), CInt(txtWait.Text))
End If
End Sub
Private Async Sub DelayExecute(action As Action, timeout As Integer)
Await Task.Delay(timeout)
action()
End Sub
End Class
Concretely in your case, your first txtTest_TextChanged starts a stopwatch. Your second txtTest_TextChanged calls _sw.Start() again, which has no effect on a running stopwatch:
Starting a Stopwatch that is already running does not change the timer state or reset the elapsed time properties.
When the first txtTest_TextChanged's continuation runs, the stopwatch's elapsed time is expected to be greater than a second: it was started more than a second ago, and since then, all that happened is that other attempts were made to start the same stopwatch. Nothing was reset.
That said, using a stopwatch here is inherently unreliable and I do not recommend continuing down this path. You cannot be sure exactly when your continuation runs.
Instead, do not measure whether your continuation should probably be cancelled, track whether it was cancelled.
The most direct way in your particular case would be to increment a counter in txtTest_TextChanged. If the counter has not been changed by the time the continuation is executed, you know txtTest_TextChanged hasn't been called a second time.
A more general way is to use the CancellationTokenSource class. Most task-based methods, including Task.Delay, have overloads accepting CancellationToken instances. You can indicate a request for cancellation through CancellationTokenSource.Cancel.
Although you do not need it in this case, in general, you can also call CancellationToken.ThrowIfCancellationRequested explicitly in specific locations during long-running operations that would not otherwise be aborted.
This is probably the dumbest question I've ever asked here, but it's hard to find answers to things like this.
I have a program with a bunch of modules/subs that each calculate a different variable. They're pretty complex, so I like to keep them separate. Now I want an earlier module to skip to another module based on user input. I thought I could use the call (sub name) method for this, but then the program returns to where the call line was and continues on that module from where it left off.
Example:
Module 1:
Sub NewPracticeSub()
Call otherpracticesub
MsgBox ("We've gone back to this sub... :(")
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub
I don't want it to return to Module 1. What can I do to have it switch control to Module 2 without it then returning to complete Module 1 upon completion of Module 2?
I feel like I just used the most confusing language possible to explain all of this, but thank you for your help anyways!!
Edit: I know I used the words module and sub interchangeably, and I know they're different. I like to keep each sub (which are each very large in my program) in their own modules because it's easier to keep track of them, and easier to explain/demonstrate the application flow to other people.
I think all you're looking for is the command Exit Sub which will make the program leave the subroutine without continuing any further, But the way you usually want to do this is, rather than calling a Sub, rather call a Function that returns a boolean value.
So, for example:
Public Function MyFunc() as Boolean
....
If [good] MyFunc = True
Else MyFunc = False
End Function
Then you could do something along the lines of:
Sub MyCallingSub()
...
If MyFunc = True then Exit Sub
Else ...
End Sub
It just adds in A LOT more felxibility and ability to choose whether you want to continue further in your sub or not.
Hope that makes sense.
Other than using the ugly End statement which I will describe below (and strongly recommend you to avoid), I'm not aware of any way to circumvent the call stack. Even John's response necessarily returns to the calling procedure, and evaluates another statement to determine whether to proceed or end.
This may yield undesirable outcomes, which is why I hesitate to recommend it, in favor of properly structuring your code, loops, etc., with respect to the call stack.
In any case, here is how you can use the End statement within your child subroutines, without needing any sort of public/global variables. This still allows you the flexibility to decide when & where to invoke the End statement, so it need not always be invoked.
Sub NewPracticeSub()
Call otherpracticesub, True
MsgBox ("We've gone back to this sub... :(")
End Sub
Sub otherpracticesub(Optional endAll as Boolean=False)
MsgBox ("We're in the other practice sub!")
If endAll then End '## Only invoke End when True is passed to this subroutine
End Sub
Why I say this method should be avoided, via MSDN:
"Note The End statement stops code execution abruptly, without
invoking the Unload, QueryUnload, or Terminate event, or any other
Visual Basic code. Code you have placed in the Unload, QueryUnload,
and Terminate events of forms and class modules is not executed.
Objects created from class modules are destroyed, files opened using
the Open statement are closed, and memory used by your program is
freed. Object references held by other programs are invalidated.
The End statement provides a way to force your program to halt. For
normal termination of a Visual Basic program, you should unload all
forms. Your program closes as soon as there are no other programs
holding references to objects created from your public class modules
and no code executing."
It will always return but that doesn't mean its a problem. I suggest you use Exit Sub as follows:
Sub NewPracticeSub()
Call otherpracticesub
**Exit Sub**
'Nothing more can execute here so its no longer a worry
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub
So I'm kind of new to VB and am just playing around with a little project, I currently need a loop that is constantly checking the systems clock to see if it's equal to a certain time.
While Not myTime.Hour = 24
If TimeOfDay = newTime Then
nfi.ShowBalloonTip(15)
intRandNumb = RandomNumber(1, 15)
dblAddMinutes = intTime + intRandNumb
newTime = TimeOfDay.AddMinutes(dblAddMinutes)
End If
End While
I have this right now, but obviously it's grinding everything to a halt and using 50% of my cpu in the process, I just would like to know what I can substitute in or change to make this loop run better and perform how I need it to.
you can add
Threading.Thread.Sleep(0),
this will cause a context switch and greatly reduce the CPU usage
Also consider using a timer object to be called every 10 or 100 ms, this will also be better in usage then having a loop
You can use
Threading.Thread.Sleep(0)
This will cause the working thread to yield the rest of it's current timeslice which will reduce the cpu usage quite a bit. However you should consider whether you really nead busy waiting for the time or if you could get away with setting a timer to count down the difference between the current time and the expected time, e.g.:
var t = new System.Timers.Timer((DateTime.Now - DateTime.Now).TotalMilliseconds);
t.Elapsed = DoSomething;
t.Start();
checking the systems clock to see if it's equal to a certain time.
There are two "correct" ways to do this:
Build a normal app that doesn't care what time it is, and set it up in windows as a schedule task.
Check the time once and calculate how long until the desired time. Then set up a timer to wait for that exact duration.
Under no circumstance should you keep polling the system clock for something like this that will just run once.
As Joel pointed out, you should try using a timer instead. I'm not sure if your app is a form or console or other, so I'll try to be generic and use System.Timers.Timer.
The code here (interval is set at 10ms, change to a value of your need):
Private timer1 As System.Timers.Timer
Const interval As Integer = 10
Sub initTimer()
timer1 = New System.Timers.Timer(10)
AddHandler timer1.Elapsed, AddressOf Me.timer_Elapsed
timer1.Start()
End Sub
Sub timer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs)
'do your stuff here
'Console.WriteLine(e.SignalTime.ToString())
End Sub
How do you limit the CPU of a while loop?
In this case, the code which is inside the while loop:
Private Sub wait(ByVal time)
Dim sw As New Stopwatch
sw.Start()
Do While sw.ElapsedMilliseconds < time And StillOpen = True
Application.DoEvents()
Loop
sw.Stop()
End Sub
But now, here is the issue. This loop is allowing the while loop to run every second, once a second, and the wait sub is causing this delay, as it should.
How can I limit the CPU that this is taking up? For some reason, my task manager says it is taking 50 CPUs to run this simple task, yet it should probably take no more than 1 or 2. Though the manager says it is taking that much CPU, my computer speed is not being affected at all, which is odd considering it is a two-year-old laptop.
I don't want any users to freak out about it, but knowing how people are these days....
Anyway, the language is vb.net. Can someone please help me?
Thanks!
EDIT: To clarify, that code is not inside the while loop itself, but a call for the subroutine is, i.e. wait(1000)
Use a timer event !!! Nearly no cpu effort.
You could always perform some kind of sleep between iterations of the loop...
I'm not familiar with VB.NET but a duration of 100-200ms will probably be more than enough to drop the CPU usage.
Eg:
Do while (...)
Application.blah();
System.Threading.Thread.Sleep(150);
End
Edit After some research, I think the function you want is: System.Threading.Thread.Sleep()
Your code is executing Application.DoEvents() constantly in the while loop, for the time duration specified in your time parameter. This will consume one core of your CPU, which is why you're seeing 50% processor usage (you have a dual-core processor, correct?). This is an ugly way to wait. You could instead call Thread.Sleep(), passing it the number of milliseconds you'd like your thread to wait.
If you'd like your application to stay responsive, you might also spin off a timer, and block the UI from any action until the timer triggers. Something like (lightly tested):
// constructor or designer code
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Tick += new EventHandler(timer_Tick);
void Wait(int interval)
{
timer.Interval = interval;
timer.Start();
BlockUIOperations(); // implement yourself
}
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
EnableUIOperations(); // implement yourself
}
Here's my attempt at a translation into VB:
'' Add a Timer object to the form named "Timer".
'' Hook its Tick event to Timer_Tick
Private Sub Wait(ByVal interval As Integer)
Timer.Interval = interval
Timer.Start()
BlockUIOperations() '' implement yourself
End Sub
Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer.Tick
Timer.Stop()
EnableUIOperations() '' implement yourself
End Sub
Well, the CPU is always running at 100% when it's running, so the only practical way to limit the CPU usage is to run bursts or loop and sleeping in between.
Laptop CPUs usually have some SpeedStep technology or equievalent that will slow down the CPU when it's not working hard, but it's not reasonable to assume that your application would have access to control that, at least not directly. You might be able to affect it indirectly by measuring the CPU usage and adjust the length of the work and sleep cycles to get the desired result.
If you don't mind blocking the current thread, you could use a WaitHandle.
Public Sub Wait(ByVal ms As Integer)
Using wh As New ManualResetEvent(False)
wh.WaitOne(ms)
End Using
End Sub
Sub Main()
Console.WriteLine("Hello World!")
Wait(5000)
Console.WriteLine("Good-Bye!")
End Sub
Of course, something more complex can be constructed depending on what you are trying to accomplish.
This is perfect as a VB.net sleep replacement. Now my console app is NOT reported as non responsive since I have no sleep commands!
Just add Imports System.Threading above your module and place this just above your sub main
Public Sub Wait(ByVal ms As Integer)
Using wh As New ManualResetEvent(False)
wh.WaitOne(ms)
End Using
End Sub
Then, in your sub main, use
wait(100)
to pause your app for 100 miliseconds.
Have fun
You should take note of if you are doing this in the main UI Thread or a thread you have spun off.
For Threads the easiest way is to just Thread.Sleep(x miliseconds)
On the main UI thread I tend to use a DoEvents function in vb.net and vb6 like this
Public Sub TimeKiller(byval secondstowait as integer)
dim tmptime as datetime = datetime.now
do while datetime.now < dateadd("s",secondstowait,tmptime)
Application.Doevents
end while
End Sub
On the question of CPU usage I look at it like this.... if you make just a hard loop that like
while true
end while
I would expect to see very high cpu usage over 50% because the UI thread is hard blocking on this.... in most cases the windows system will limit the cpu usage of any given program so that its threads dont block the entire system.
The DoEvents ensure that windows message pumps fire correct and respond to correct. It also ensures that the garbage collector fires on time.
Also if you have other threads spun up off of your UI.Thread your UI.Thread can respond to events fired from these other threads....
In such cases where your calling form controls from other threads and do form.InvokeRequired routines will be able to respond correctly.
Also The only time you should be hard looping on the MainUI thread is when it is in response to some user activity and you need to put waits in for the user to see progress of something....
If it is some kind of automated process that is always running... look to moving it to another thread.
Or if its something that runs periodically on a timer or a time that kicks off a thread.
Somebody please tell me if I am wrong on these assumptions....
Not sure about the Using wh As New ManualResetEvent(False) wh.WaitOne(ms) as I have never heard of that and have no idea what that does.