I have a Start and Stop button on a form that start and stop a repeating SQL query which sends data to a pubnub channel. When I fire up the form and click start, I see what I expect on the subscribed clients. However, if I click stop then start again, I now get duplicate data. A third time gives me triplicate data, etc. What is causing this? Here are the start and stop methods:
private void btnQuery1Start_Click(object sender, EventArgs e)
{
lblQuery1Status.Text = "Status: Running";
btnQuery1Start.Enabled = false;
txtQuery1Interval.Enabled = false;
btnQuery1Stop.Enabled = true;
query1Timer.Elapsed += new ElapsedEventHandler(doQuery1);
query1Timer.Interval = Convert.ToInt32(txtQuery1Interval.Text) * 1000;
query1Timer.Enabled = true;
}
private void btnQuery1Stop_Click(object sender, EventArgs e)
{
btnQuery1Start.Enabled = true;
btnQuery1Stop.Enabled = false;
txtQuery1Interval.Enabled = true;
query1Timer.Enabled = false;
lblQuery1Status.Text = "Status: Stopped";
}
I can post doQuery1 if necessary, but it's using an OdbcConnection and data reader to get a single integer result then it's serializing it with Newtonsoft.Json and sending it using Pubnub.publish(). I'm hoping though that this is something obvious I'm just missing in the btnQuery1Start_Click() method above.
No, you have a single timer - but you're adding an event handler to it every time you click start:
query1Timer.Elapsed += new ElapsedEventHandler(doQuery1);
Just move that line into wherever you construct the timer, so it only gets added once, and it'll be fine.
(I'd personally rewrite it as query1Timer.Elapsed += doQuery1;, but that's your call...)
Every time you press start you add the event handler to the timer, but on stop you don't remove it.
Every time you click start you are executing the line:
query1Timer.Elapsed += new ElapsedEventHandler(doQuery1);
This adds a new event handler each time it is called causing your doQuery1 method to be executed multiple times
Related
I ran into a problem the other day. I have found out why it's happening, but I've never had a run in with such a problem so I don't know how to solve it.
I have an application where in the DashboardView (the main view) a DispatcherTimer is started in the DashboardViewModel. When the Timer ticks, we get the data from the database, this list is databound between the View and the ViewModel. When there is new data that caused the database to change, a sound will play.
The user can go to other Views. When the user goes back to the DashboardView, the DashboardViewModel is again created and so is the DispatcherTimer.
Now there are 2 Timers and they both fire the Tick event, creating a confusing scenario for the user.
This is my observation of what happens in the application right now:
My Timer ticks every minute. When I start the application, DashboardView #1 opens. DashboardViewModel #1 starts and so does DispatcherTimer #1.
I switch to a different view, and make an update to the data (a new email) so when the Timer ticks, the list in the DashboardView will change and a sound is played.
When Timer #1 is at 30 seconds, I switch to the DashboardView, which is newly created thus creating View&ViewModel&Timer #2.
After 1 minute, Timer #1 ticks, there is new data so it updates the DB and plays a sound, yet the list in the View doesn't update.
I think that this is because View #2 is showing over #1. I know because otherwise I would see an overlay saying it's refreshing.
View #2 is databound to ViewModel #2. Timer #1 updated ViewModel #1, so the changes won't show as we can't see View #1 as it's replaced/overlapped by View #2.
After 1 min 30 seconds, Timer #2 ticks, gets the data from the DB, doesn't play a sound as the DB was already brought up-to-date by Timer #1, and shows the data in the new state.
(I hope that made sense)
So, TLDR: There are 2 Timers running while only 1 should be active (the newest one, I think).
How can I achieve this?
Here's (part of) the DashboardViewModel as I have it now:
namespace QRM.ViewModel
{
class DashboardListViewModel : INotifyPropertyChanged
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
DBServer dbServer = new DBServer();
#region Constructor
public DashboardListViewModel()
{
log.Info("Dashboard Initializing - Starting...");
MyObservableCollection<View_server> listDashboard = new MyObservableCollection<View_server>();
ListDashboard = dbServer.ReadDashboard();
listBoxCommand = new RelayCommand(() => SelectionHasChanged());
// Refresh to get all new emails, errors, etc.
GetListDashboard();
IsRefreshing = Visibility.Collapsed;
// Make a timer to renew the data in the Dashboard automatically.
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = Properties.Settings.Default.Timer_interval; // hours, minutes, seconds.
timer.Start();
//Receive the Notification sent after DashboardDetailsViewModel has handled the button commands, and call a respond method for the List.
App.Messenger.Register("RefreshServers", (Action)(() => GetListDashboard()));
App.Messenger.Register("ClearSelection", (Action)(() => SelectedServer = null));
App.Messenger.Register("ErrorSolved", (Action)(() => KeepSelection(selectedServer)));
App.Messenger.Register("WarningSound", (Action)(() => HasNewError = true));
log.Info("Dashboard Initializing - Done.");
}
#endregion
#region Get list dashboard
private void GetListDashboard()
{
HasNewError = false;
log.Info("Dashboard - Checking for Email...");
// The old Outlook class and methods
//EmailManager checkMail = new EmailManager();
//checkMail.GetEmail();
// First, check for mail.
IMAPManager checkMail = new IMAPManager();
checkMail.GetEmail();
log.Info("Dashboard - Checking for linked Errors...");
// Check if the emails have Errors linked to them. If not, add the Error from the Email to the DB
ErrorManager checkError = new ErrorManager();
checkError.GetNewErrors();
log.Info("Dashboard List - Starting...");
// Load the dashboard.
ListDashboard = dbServer.ReadDashboard();
System.Diagnostics.Debug.WriteLine("REFRESHED THE DASHBOARD");
log.Info("Dashboard List - Done.");
}
private void KeepSelection(View_server keepSelection)
{
GetListDashboard();
SelectedServer = keepSelection;
SelectionHasChanged();
}
#endregion
#region Timer
//This method runs every time the timer ticks.
private async void timer_Tick(object sender, EventArgs e)
{
log.Info("Dashboard - Refreshing...");
System.Diagnostics.Debug.WriteLine(">>Timer tick");
IsRefreshing = Visibility.Visible;
// To make sure the overlay is visible to the user, let it be on screen for at least a second (2x half a second)
await Task.Delay(500);
if (selectedServer != null)
{
KeepSelection(selectedServer);
}
else
{
GetListDashboard();
}
// 2nd half second.
await Task.Delay(500);
IsRefreshing = Visibility.Collapsed;
if (hasNewError == true)
{
System.Diagnostics.Debug.WriteLine("List has new error");
PlayWarningSound();
HasNewError = false;
}
else
{
System.Diagnostics.Debug.WriteLine("List has no new error");
HasNewError = false;
}
System.Diagnostics.Debug.WriteLine(">>End timer");
log.Info("Dashboard - Refreshed.");
}
#endregion
}
}
There are a few issues going on here. Let's start with the most basic first:
Cleanup
When the DashboardListViewModel is disposed of or closed, you need to unwire your DispatcherTimer.Tick event handler, invoke .Stop() and then call .Finalize(). MSDN. This will ensure that your System.Windows.Threading.DispatcherTimer is properly cleaned up.
Async / Await & Event Handlers
Also, the DispatcherTimer.Tick event handler is defined as async void. This is the incorrect usage of the async keyword. Instead use this:
private void timer_Tick(object sender, EventArgs e)
{
log.Info("Dashboard - Refreshing...");
System.Diagnostics.Debug.WriteLine(">>Timer tick");
IsRefreshing = Visibility.Visible;
// To make sure the overlay is visible to the user, let it be on screen for at least a second (2x half a second)
Thread.Sleep(500);
if (selectedServer != null)
{
KeepSelection(selectedServer);
}
else
{
GetListDashboard();
}
// 2nd half second.
Thread.Sleep(500);
IsRefreshing = Visibility.Collapsed;
if (hasNewError == true)
{
System.Diagnostics.Debug.WriteLine("List has new error");
PlayWarningSound();
HasNewError = false;
}
else
{
System.Diagnostics.Debug.WriteLine("List has no new error");
HasNewError = false;
}
System.Diagnostics.Debug.WriteLine(">>End timer");
log.Info("Dashboard - Refreshed.");
}
I usually never advise using Thread.Sleep but since you're already in the context of a Threading Timer this makes sense.
One last concern
Are you certain that the App.Messenger.Register can be invoked multiple times, as it occurs every time your view model is instantiated? I would have imagined this would be something you'd only ever want to do once, in static context.
I've initialized a windows form timer in my constructor (and set it running at an appropriate time by writing "pTimer.Enabled=true". In the constructor-
pTimer = new Timer();
pTimer.Enabled = false;
pTimer.Interval = timerSpeed;
pTimer.Tick += new EventHandler(pTimer_Tick);
Inside the timer callback, I'm checking to see if my work has been completed. Once it is, I'd like to stop the timer (in the sense that it won't keep calling the pTimer_Tick callback) and close a streamwriter that's doing the logging.
private void pTimer_Tick(object sender, EventArgs e)
{
bool tempFinished = finished();
log("finished() in timer tick = " + tempFinished.ToString());
if (tempFinished)
{
if (pTimer.Enabled == true)
{
log("We are going to stop the timer");
pTimer.Enabled = false;
Application.DoEvents();
log("We are going to close the streamwriter");
//myStreamWriter.Close();
}
}
}
The following behaviour seems to occur:
After the first time that finished()==true in the pTimer_Tick
callback, we log "finished() in timer tick = True", and the code
reaches pTimer.Enabled = false
Originally, I had not commented out closing the streamwriter and this was generating an error because it was continuing to attempt to write to code being logged from the pTimer_Tick callback
If we comment out myStreamWriter.Close(); (as written above), after 1. occurs, the code indeed seems to keep reentering the pTimer_Tick callback, it runs the finished() method when it reaches the "bool tempFinished=finished()" line, and the internal log to the finished() method indicates that it is going to output "true" again as expected, but the logging on the next line " log("finished() in timer tick = " + tempFinished.ToString());" is never down, and the next thing that appears in the log is the code accessing the finished() function again.
Does anyone have any suggestions about what could be wrong here?
Thanks!
Chris
You are starting and stopping the timer by setting the Enabled property. Although you claim in the comments that this is sufficient, I'm not convinced.
Try using the Start and Stop timer functions instead.
So I'm trying to run an event every 5 seconds. Seems to work using System.Timers.Timer to some extend but it seems to be skipping sometimes, not even responding late, just plain skipping it.
Anything I could do about this?
internal void DetermineScreenCapping()
{
System.Timers.Timer ScreenCapTimer = new System.Timers.Timer();
/// Initialize the screencapper (doesn't enable it yet)
// Tell the timer what top do when it elapses
ScreenCapTimer.Elapsed += new ElapsedEventHandler(ExecuteCode);
// Set it to go off every five seconds
ScreenCapTimer.Interval = 5000;
// And start it
ScreenCapTimer.Enabled = true;
}
private void ExecuteCode(object source, ElapsedEventArgs e)
{
if (IsCurrentlyWorking == true)
{
Execute Code
}
}
The problem indeed wasn't the timer not doing it's job. It was the code being executed that had some problems that couldn't even be seen debugging for some reason.
I changed the code and the timer works properly now ^^
So I have two event handlers button1_Click() and button2_Click()
In button1_Click() I have something running like this:
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
//DoStuff
}
But button2_Click is supposed to be a stop button, and stop button1 early.
How do I go about this?
Thanks for the help. I saw this article here about it, but couldn't get it to work.
Windows.Forms answer
The least sophisticated method is this:
private bool m_stop;
private void button1_Click (object s, EventArgs ea)
{
try
{
// Don't forget to disable all controls except the ones you want a user to be able to click while your method executes.
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
Application.DoEvents ();
// DoEvents lets other events fire. When they are done, resume.
if (m_stop)
break;
//DoStuff
}
finally
{
// Enable the controls you disabled before.
}
}
private void button2_Click (object s, EventArgs ea)
{
m_stop = true;
}
It has the distinct advantage of letting you execute button1_Click on the UI thread, still lets the UI respond to your stop button.
It has a disadvantage that you must protect against reentrancy. What happens if they click your button1 while button1_click is already executing!?!?
Edit: Another way I have used is to use a Timer instead of a loop. Then, the stop method just stops the timer.
As much as I understood, correct me if I'm wrong, you're on single thread.
Wired, but you can check for single boolean value inside the your While loop, just as post suggested.
May be to make life easier (may be this is what "couldn't get it to work" means) is inside loop call
1) Windows Forms: Application.DoEvents()
2) WPF (little bit more tricky) : DoEvents in WPF
This to make breathe system.
You need to start the process inside the button1 in new thread, and when you press the button2 flag a local variable to false to stop the loop. like:
using System.Threading;
private volatile bool _requestStop = false;
private readonly object _oneExecuteLocker = new object();
private void OnButton1Click(ojbect sender, EventArgs e)
{
new Thread(() =>
{
if (Monitor.TryEnter(_oneExecuteLocker))
{//if we are here that is means the code is not already running..
try
{
while (!_requestStop)
{
//DoStuff
}
}
finally
{
Monitor.Exit(_oneExecuteLocker);
}
}
}){ IsBackground = true }.Start();
}
private void OnButton2Click(object sender, EventArgs e)
{
_requestStop = true;
}
Notes:
When ever you want to update a UI control inside the newly created thread you should use contorl.Invoke(/*the code get/set or call method in the UI*/).
The Monitro.Enter is just to be sure that your code will not executed multiple time per click if it already running.
I am developing a windows application for my company that runs on the server. It is a multi threaded application, and i am using Thread Pool for that.
My Application Email module consists of 3 major methods. 1st method gets new campaigns from database, second method decides to whom the campaign is going to be sent via email and third method sends it.
When I start the application, 1st method goes into Thread Pool, if there is a new campaign, 2nd method is invoked with the campaign info. But while these all are happening, first method has to check database in every three seconds if there is a new campaign or not.
I am not sure if I have to use System.Windows.Forms.Timer class for that or System.Threading.Timer??
And I am not sure how to implement it? Am I going to use Invoke Function to invoke thread outside the main UI? Could you please post an example code and suggest best practices??
Thanks a lot
Here is my code :
private void btnStart_MouseClick(object sender, MouseEventArgs e)
{
smartThreadPool = new SmartThreadPool();
workItemGroup = smartThreadPool.CreateWorkItemsGroup(1);
workItemGroup.QueueWorkItem(CheckNewCampaigns);
//smartThreadPool.QueueWorkItem(new WorkItemCallback(this.CheckNewCampaigns));
}
private object CheckNewCampaigns(object state) // 1st method
{
StringBuilder builder = new StringBuilder();
IEnumerable<Campaigns> CampaignsList = DatabaseManager.GetCampaignsList(DatabaseManager.GetNewCampaigns());
foreach (Campaigns Campaign in CampaignsList)
{
builder.AppendFormat("New Campaign Arrived($) --> {0}\r\n", DateTime.Now.ToLongTimeString());
builder.AppendFormat("CampaignID --> {0}\r\n", Campaign.CampaignID);
builder.AppendFormat("CustomerID --> {0}\r\n", Campaign.CustomerID);
builder.AppendFormat("ClientID --> {0}\r\n", Campaign.ClientID);
builder.AppendFormat("Title --> {0}\r\n", Campaign.Title);
builder.AppendFormat("Subject --> {0}\r\n", Campaign.Subject);
builder.AppendFormat("Status --> {0}\r\n", Campaign.Status);
}
Console.WriteLine(builder.ToString());
workItemGroup.QueueWorkItem(new WorkItemCallback(this.PrepareCampaignEmail), 2);
return true;
}
private object PrepareCampaignEmail(object CampaignID) // Second Method
{
int campaignID = (int)CampaignID;
IEnumerable<Campaigns> CampaignDetailsList = DatabaseManager.GetCampaignsList(DatabaseManager.GetCampaignDetails(campaignID)); // bir tane campaign gelmekte
IEnumerable<Subscribers> SubscribersList = DatabaseManager.GetCampaignSubscribersList(DatabaseManager.GetCampaignSubscribers(campaignID));
ArrayList test = new ArrayList();
DataTable dtCustomValuesForCampaign = DatabaseManager.GetCustomValuesForCampaign(campaignID);
foreach (Subscribers subscriber in SubscribersList)
{
workItemGroup.QueueWorkItem(new WorkItemCallback(this.SendEmail), subscriber.Email);
}
return true;
}
In your situation, since it's a Windows Forms application and you'll potentially want to update the UI in the timer event handler, I'd suggest using Windows.Forms.Timer.
Using Windows.Forms.Timer is pretty easy. In the design view of your form, select the Timer from the Toolbox and drop it on your form. Then, click on it to set the properties. You want to set Interval to 3000 (that's 3000 milliseconds), and Enabled to False.
On the Events tab, double-click the Tick event and the IDE will create a handler for you. You want the event handler to look something like this:
private void timer1_Tick(object sender, EventArgs e)
{
var stateObj = // create your state object here
CheckNewCampaigns(stateObj);
}
You'll need to start the timer (set Enabled to True). You can do that in your Create event handler, or you can enable it when the user hits the Start button. You can also stop it at any time by setting Enabled to False.
System.Thread.Timer and Windows.Forms.Timer act differently.
The System.Thread.Timer will run on a system thread. It has an internal thread pool, so it won't be run on one of the threads you explicitly created. Every interval, one of the threads in the Timer's pool will run the callback you initialized the object with.
The Windows.Forms.Timer will raise the Tick event on the current thread and will do so every interval, until you disable it.
I can't tell you which is more appropriate for your situation; it depends on whether you want to run the timer on the UI thread, as well as other factors.
Try this:
public void EnableTimer()
{
if (this.InvokeRequired)
this.Invoke(new Action(EnableTimer));
else
this.timer1.Enabled = true;
}