I am coding in VB.Net, VS 2008.
I wrote a console app that consumes 2 web methods from a web site application. I need to enhance this console app so that it launches the web methods continuously, perhaps every x minutes (during business hours), but never before the last invocation has terminated, whose duration may vary, depending on how many accounts there are to process.
Originally, I scheduled the application using Task Scheduler, but I think this doesn't prevent two invocations at the same time.
Although I have seen many posts on using timers, I haven't found exactly what I need.
So far I have:
Dim aTimer As New System.Timers.Timer()
AddHandler aTimer.Elapsed, AddressOf TriggerWebMethods
' Set the Interval to 10 minutes:
aTimer.Interval = 1000 * 60 * 10 '(1 second * 60 = 1 minute * 10 = 10 minutes)
aTimer.Enabled = True
aTimer.AutoReset = True
When should Timer.Elapsed be used vs. Timer.Tick?
What is the difference between Timer.Enabled vs Timer.Start, and should I be selecting just one?
I would like the 2nd web method to kick off when the first one is done.
I'd like to keep this as simple as possible. Thank you for all help.
If you are dealing with a System.Timers.Timer, then you'd only have the Elapsed event available. If a System.Windows.Forms.Timer, then you'd use the Tick event. You're not writing a WinForms app so you would be using the System.Timers.Timer.
Personally, I would only use the Enabled property to see if the timer has been started. I wouldn't use it to start or stop it. Using the Start() or Stop() method makes it very clear what's happening to the timer.
If your web methods execute synchronously, you could just call them one after the other in your TriggerWebMethods() method. The second will not be called until the first completes.
Sub TriggerWebMethods(source As Object, e As ElapsedEventArgs)
FirstWebMethod()
SecondWebMethod()
End Sub
If asynchronously, you'd have to register a callback on the first web method to execute the second when it completes. In VB, I believe you can use the second directly as the callback, depending on how you make the asynchronous call. (Sorry, my VB is very rusty now so might not be 100% correct syntax)
Sub FirstWebMethod()
' ...
End Sub
Sub SecondWebMethod()
' ...
End Sub
Sub TriggerWebMethods(source As Object, e As ElapsedEventArgs)
Dim first As Action = AddressOf FirstWebMethod
first.BeginInvoke(AddressOf SecondWebMethod, first)
End Sub
Just to add a little to Jeff M's answer. The Timer.Elapsed Event has the following note.
If the SynchronizingObject property is null, the Elapsed event is
raised on a ThreadPool thread. If the
processing of the Elapsed event lasts
longer than Interval, the event might
be raised again on another ThreadPool
thread. In this situation, the event
handler should be reentrant.
Since you're in a Console app you can either hand roll you own SynchronizingObject or you can set the AutoReset to False, and change your TriggerWebMethods to have a start at the end. You may even want to offset the interval to take into consideration the amount of processing time.
e.g.
Dim start As Date = Now
'do stuff
Dim ts As TimeSpan = Now - start
Dim i As Integer = (1000 * 60 * 10) - ts.TotalMilliseconds
Dim aTimer As Timers
aTimer.Interval = i
Related
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.
I have my service working mostly as it should, but it should only do it's work once every day.
To arrange this, my boss recommended I sleep the worker thread until 7am tomorrow:
This Thread.Sleep call is copied directly from a similar service he wrote that apparently works, but this always throws an ArgumentOutOfRangeException - the value returned is a negative.
Private Sub startExport()
#If Not Debug Then
Thread.Sleep(1000 * 60 * 1)
#End If
While runReportExport
Try
runExport()
Catch ex As Exception
el.WriteEntry("Error exporting data: {1}")
Finally
'sleep thread until tomorrow 7am
Thread.Sleep(DateTime.Now.Subtract(Date.Today.AddDays(1).AddHours(7)))
End Try
End While
End Sub
I'm fairly confused about how this all works so if anyone can explain the whole timespan thing for me, I'd greatly appreciate it.
On the other hand, my friend recommends I manage the thread execution in a different way.
Here's what He recommended I do:
Private lastExecute As DateTime = DateTime.Now
Private Overrides Sub OnStart(ByVal args() As String)
startService()
End Sub
Private Sub startService()
Dim nextExecute = lastExecute.AddDays(1)
If nextExecute >= DateTime.Now Then
lastExecute = DateTime.Now
tWorker = New Thread(AddressOf startExport)
tWorker.IsBackground = True
tWorker.Start()
End If
End Sub
He said this would execute the worker thread once on startup and not again for another day. While this code does work without error, it doesn't stop the service loop from executing the worker thread over and over again (currently it executes a 2nd time as soon as it finished it's first run)
Personally I'm open to either approach, I can't seem to get either working.
Basically, all I need at the end of the day is a service that exports data once daily.
My boss' Thread.Sleep option seems the simpler, but my friend's suggestion seems like better practice.
Can anyone help me to get this sorted (at this point I don't care which one I use, just so long as it works)?
DateTime.Now.Subtract(Date.Today.AddDays(1).AddHours(7)))
You subtract a future time from the current time. That always produces a negative value. Kaboom.
You'll need to invert that, subtracting the current time from the future time. Spelled out for clarity and avoiding the race on DateTime.Now :
Dim today = DateTime.Now
Dim tomorrow = today.Date.AddDays(1).AddHours(7)
Dim wait = tomorrow - today
Thread.Sleep(wait)
You will need to do something useful when the service is stopped. Best done with a ManualResetEvent, you'll get the sleep from its WaitOne(wait) method. Btw, extremely wasteful to have a thread sleep for that long and not doing anything useful. Use a Timer instead.
Another approach, and one I use, to run a specific task once a day is to set the task up in a timer. In my case I want the task to run at 12:05am each morning. Accordingly, when my program starts I set the initial interval of the timer such that the first tick will happen at 12:05 am the next day. After that, once a day, at the end of the code the executed as part of the tick I once again reset the timer interval such that the next tick will happen at 12:05am the next day.
...
Timer1.Interval = MillisecondsToMidnight() + 300000 ' next tick 12:05:00 tomorrow
...
Private Function MillisecondsToMidnight() As Integer
Dim ReturnValue As Integer
Dim ts As TimeSpan
Dim Tomorrow As DateTime = Today.AddDays(1)
ts = Tomorrow.Subtract(Now)
ReturnValue = ts.TotalMilliseconds()
ts = Nothing
Return ReturnValue
End Function
I'm using a Threading.DispatcherTimer and every tick of the timer runs a subroutine.
Is it possible that if the subroutine takes longer then 1s for the processor to finish that it will mess up the time of the timer that is counting in seconds?
Try
timercount = Nothing
timercount = New DispatcherTimer()
timercount.Interval = TimeSpan.FromSeconds(1)
timercount.Start()
AddHandler timercount.Tick, AddressOf TickMe 'Every Second the 'TickMe' Method runs
Catch ex As Exception
' MessageBox.Show(ex.Message)
End Try
Private Sub TickMe()
CL(0).Actual = H.Actual
TextBoxSerialNumber.Focus()
IntSec = IntSec + 1
H.ActualBoxTime = H.ActualBoxTime.AddSeconds(1)
CL(0).ActualBoxTime = H.ActualBoxTime
If IntSec Mod 60 = 0 Then
H.Actual = H.Actual + 1
CL(0).Actual = H.Actual
End If
CL(0).TargetBox = H.TargetBox
PopulateCutGrid(CL)
End Sub
I've found after 50-60 minutes of counting it's off by almost 10 minutes!
From the MSDN article regarding DispatchTimer:
Timers are not guaranteed to execute exactly when the time interval occurs, but they are guaranteed to not execute before the time interval occurs. This is because DispatcherTimer operations are placed on the Dispatcher queue like other operations. When the DispatcherTimer operation executes is dependent on the other jobs in the queue and their priorities.
If you need something more precise, I would recommend System.Threading.Timer. Each time the method of this timer is invoked, a ThreadPool thread is used. From this article:
The callback method executed by the timer should be reentrant, because it is called on ThreadPool threads. The callback can be executed simultaneously on two thread pool threads if the timer interval is less than the time required to execute the callback, or if all thread pool threads are in use and the callback is queued multiple times.
Also forgot to mention that you'll need to use Invoke and InvokeRequired for WinForms or Dispatcher for WPF (I think, since I don't use WPF).
Edit 1:
Regarding your comment about needing total seconds, you can just store the original DateTime somewhere, and total seconds would be calculated using (DateTime.Now - StoredDateTime).TotalSeconds(). I would also recommend reading this article about threading in WPF to see if it applies in your case.
Edit2:
Actually, since I think the only thing you're after is a precise count of elapsed time, you could use a Stopwatch to keep track of time. Try using the regular DispatchTimer and just refer to the stopwatch each "tick" of that timer. That way, the Stopwatch is what keeps track of time, not the timer that could be used just for updating the UI.
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.