I'm building an alarm clock application and using System.Threading.Timer. After the timer is elapsed I wanna show usercontrol to allow user to choose either to wake up or to snooze. After adding a new control or form in callback the form is just closing.
private void setTimerButton_Click(object sender, EventArgs e)
{
try
{
alarmDate = setAlarmDateTimePicker.Value - DateTime.Now;
var alarmClock = new System.Threading.Timer(AlarmCallback, null, alarmDate, TimeSpan.Zero);
addedAlarmTextBox.Text = setAlarmDateTimePicker.Text;
}
catch
{}
finally
{
setAlarmDateTimePicker.Value = DateTime.Now;
}
}
private void AlarmCallback(object state)
{
this.Controls.Add(new AlarmBeepsForm());
}
As far as I know, it has something with the threads, but I'm quite new to programming and I don't understand what's wrong.
How can I change the code to successfully add the control or should I use another type of timer?
Unless you set Control.CheckForIllegalCrossThreadCalls to false, you should call Control.Invoke to switch to the thread that created the control handle.
private void setTimerButton_Click(object sender, EventArgs e)
{
try
{
alarmDate = setAlarmDateTimePicker.Value - DateTime.Now;
var alarmClock = new System.Threading.Timer(AlarmCallback, null, alarmDate, TimeSpan.Zero);
addedAlarmTextBox.Text = setAlarmDateTimePicker.Text;
}
catch
{}
finally
{
setAlarmDateTimePicker.Value = DateTime.Now;
}
}
private void AlarmCallback(object state)
{
this.Invoke(new Action(() => this.Controls.Add(new AlarmBeepsForm()));
}
Related
I'm writing a program that reads every second data from a serialPort and save it in a textfile/show it on GUI. The reading starts and end with an buttonclick.
I tried some different timers to solve this but every timer brings some trouble(see below).
My tryouts:
serialPort1.ReadTimeout = 2000;
System.Timers.Timer:
private void timer1_Tick(object sender, EventArgs e)
{
if (!serialPort1.isOpen)
{
serialPort1.Open();
}
serialPort1.WriteLine("INFO"); //Send data command
string data = serialPort1.ReadLine();
serialPort.Close();
editData(data); //Method for GUI update and textfile log
}
Can easily started and stopped with timer1.Start() and timer1.Stop(). The problem is, System.Timers.Timer runs on GUI Threard and freezes the GUI while serialPort.read and serialPort.Close() is called.
Backgroundworker:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (backgroundWorker1.CancellationPending == false)
{
if (!serialPort1.isOpen)
{
serialPort1.Open();
}
serialPort1.WriteLine("INFO");
string data = serialPort1.ReadLine();
serialPort.Close();
Invoke((MethodInvoker)(() => editData(data)); //Method for GUI update and textfile log
}
}
Runs asynchronlly. I need to run the programm ~every second.
System.Timers.Timer calls Backgroundworker:
private void timer1_Tick(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (!serialPort1.isOpen)
{
serialPort1.Open();
}
serialPort1.WriteLine("INFO");
string data = serialPort1.ReadLine();
serialPort.Close();
Invoke((MethodInvoker)(() => editData(data)); //Method for GUI update and textfile log
}
This works fine until the data reading process takes longer or a serialPort.readTimeout occur. Backgroundworker can only run once. So I think this isn't an option.
System.Threading.Timers:
System.Threading.Timer timer;
timer = new System.Threading.Timer(_ => readSerialPort(), null, 0, 950);
private void readSerialPort()
{
if (!serialPort1.isOpen)
{
serialPort1.Open();
}
serialPort1.WriteLine("INFO");
string data = serialPort1.ReadLine();
serialPort.Close();
Invoke((MethodInvoker)(() => editData(data)); //Method for GUI update and textfile log
}
This works fine but the problem is, I can't stop and restart the reading.
Do anyone have an idea which timer I should use in this case?
About System.Threading.Timer
var timer = new System.Threading.Timer(cb, null, 1000, 1000); // init
timer.Change(Timeout.Infinite, Timeout.Infinite); // stop
timer.Change(0, 1000); // start
P.S. Dont forget to dispose timer
You can implement this logic using a thread. A meta code is below:
var stopEvent = new ManualResetEvent(false);
var thread = new Thread(() => {
if (!serialPort1.isOpen)
{
serialPort1.Open();
}
try
{
while (!stopEvent.WaitOne(0))
{
try
{
serialPort1.WriteLine("INFO");
string data = serialPort1.ReadLine();
Invoke((MethodInvoker)(() => editData(data)));
}
catch (Exception)
{
// Handle exception, e.g. a reading timeout
}
stopEvent.WaitOne(1000); //Thread.Sleep(1000);
}
} finally
{
serialPort.Close();
}
});
thread.Start();
//call it to stop the loop.
stopEvent.Set();
}
You can implement more complex logic like stopping and resuming readings. Just use more events. If you are not familiar with events you can use just use boolean variables but define that they are volatile.
I have a class SendCountingInfo() and it will send a message to server every 5 minutes. The code inside this class are:
public void StartSendCountingInfo()
{
DoStartSendCountingInfo(300000);
}
private void DoStartSendCountingInfo(int iMiSecs)
{
_pingTimer = new System.Timers.Timer(iMiSecs);
_pingTimer.Elapsed += new System.Timers.ElapsedEventHandler(pingTimer_Elapsed);
_pingTimer.Start();
}
void pingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
PingRemoteHost();
}
When I try to call it in the Windows Form class, it didn't work.
But, when I remove the timer and call PingRemoteHost() directly, it works. However, the form didn't load properly. It shows blank screen but the method PingRemoteHost() work.
Here is the code inside the windows form:
private void Layout_Load(object sender, EventArgs e)
{
tSystemChecker = new System.Timers.Timer(1000);
tSystemChecker.Elapsed += new System.Timers.ElapsedEventHandler(tSystemChecker_Elapsed);
tSystemChecker.Start();
this.WindowState = FormWindowState.Maximized;
}
void tSystemChecker_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
UIThreadWork(this, delegate
{
try
{
SuspendLayout();
DoCheckHardwareStatus();
DoCheckLanguage();
SendCountingInfo sci = new SendCountingInfo();
sci.StartSendCountingInfo();
}
catch (Exception exp)
{
System.Diagnostics.Debug.WriteLine(exp.Message);
System.Diagnostics.Debug.WriteLine(exp.Source);
System.Diagnostics.Debug.WriteLine(exp.StackTrace);
}
ResumeLayout(true);
});
}
Do you have any idea what's wrong?
Use a thread and see if the problem persist
using System.Threading;
//Put this where you want to start the first timer
Thread thread = new Thread(dowork =>
{
public void StartSendCountingInfo();
}
If you are updating the GUI use for your controls
guicontrol.BeginInvoke((MethodInvoker)delegate()
{
guicontrol.Text = "aa";
//etc
});
I've looked in many places for this but still haven't found a solution. What I'm trying to achieve is being able to use BackgroundWorker on a timed basis. Here's an example:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.DoWork += isOnline;
isDbAvail.RunWorkerCompleted += rewriteOnlineStatus;
}
private void rewriteOnlineStatus(object sender, RunWorkerCompletedEventArgs e)
{
Subs.Connection connection = new Subs.Connection();
changeStatus(connection.isDbAvail());
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
System.Threading.Thread.Sleep(8000);
}
}
public void changeStatus(bool status)
{
if (status)
{
serverStatusVal.Text = "Connected";
serverStatusVal.ForeColor = System.Drawing.Color.DarkGreen;
}
else
{
serverStatusVal.Text = "Not connected";
serverStatusVal.ForeColor = System.Drawing.Color.Red;
}
}
What's happening here is that the isOnline method checks if there is a connection to the database (just an example) every 8 seconds and changes the text accordingly. What I've noticed though, is that the while loop inside the isOnline method causes the rewriteOnlineStatus method never to fire because it runs indefinitely. Is there another workaround to this?
I suggest you use BackgroundWorker.ReportProgress, and check connectivity in the background thread.
Something like this:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.WorkerReportsProgress = true;
isDbAvail.DoWork += isOnline;
isDbAvail.ProgressChanged += rewriteOnlineStatus;
isDbAvail.RunWorkerAsync();
}
private void rewriteOnlineStatus(object sender, ProgressChangedEventArgs e)
{
changeStatus((bool)e.UserState);
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
Subs.Connection connection = new Subs.Connection();
isDbAvail.ReportProgress(0, connection.isDbAvail);
System.Threading.Thread.Sleep(8000);
}
}
Now the BackgroundWorker is doing the work, and reporting back to the UI thread via ProgressChanged.
Context: I am playing music through a media element, and using a slider to display the point in the song that it is at. That updating is done in a backgroundworker, for obvious reasons.
private void bgPlay_DoWork(object sender,DoWorkEventArgs e)
{
while (isMediaPlaying)
{
this.Dispatcher.Invoke((Action)(() =>
{
timelineSlider.Value = mediaElement1.Position.TotalMilliseconds;
}));
}
}
private void Library_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
try
{
mediaElement1.Stop();
isMediaPlaying = false;
mediaElement1.Source = new Uri(songData[Library.SelectedIndex].Location);
mediaElement1.Volume = (double)volumeSlider.Value;
mediaElement1.Play();
isMediaPlaying = true;
bgPlay.RunWorkerAsync();
}
catch(Exception ex) {
F.MessageBox.Show(ex.ToString());
}
}
When I play a song, then double click on a different one, the background worker is still looping and throws an exception because it reaches bgPlay.RunWorkerAsync(); before the previous instance has finished. I tried to use the isMediaPlaying bool to tell the backgroundworker when to exit the loop, but the main thread reaches bgPlay.RunWorkerAsync(); before it finishes.
You are suffering of a common mistake when one is barely starting to program with threading, a race condition
I'd advise rewriting the code like this:
private static String threadingLock = "";
private void bgPlay_DoWork(object sender,DoWorkEventArgs e)
{
while (true)
{
lock(threadingLock) {
if(!isMediaPlaying)
break;
}
this.Dispatcher.Invoke((Action)(() =>
{
timelineSlider.Value = mediaElement1.Position.TotalMilliseconds;
}));
}
}
private void Library_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
try
{
lock(threadingLock) {
isMediaPlaying = false;
}
mediaElement1.Stop();
mediaElement1.Source = new Uri(songData[Library.SelectedIndex].Location);
mediaElement1.Volume = (double)volumeSlider.Value;
mediaElement1.Play();
isMediaPlaying = true;
bgPlay.RunWorkerAsync();
}
catch(Exception ex) {
F.MessageBox.Show(ex.ToString());
}
}
As a friendly tip, add a Thread.sleep(200) before invoking the update on the slider. It will reduce cpu usage without affecting the functionality of your application.
This case is using C# WPF. I want to instantly disable a button after clicking it to prevent clicking it twice in short succession. I disabled the button in OnClick_Event but still clickable.
Part of source is as below.
private void Button_Click_UpdateBurndownChart(object sender, RoutedEventArgs e)
{
if(threadNotWorking)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
private void startWorkThread()
{
... ...
//after finish required process
updateButton.IsEnabled = true;
}
Is there any way to accomplish this?
you may want to use a dispatcher, there is probably a threading problem (callback function running on seperate thread and trying to access ui which runs on another thread). try this . .
updateButton.Dispatcher.BeginInvoke(
new ThreadStart(() => updateButton.IsEnabled = false),
System.Windows.Threading.DispatcherPriority.Input, null);
instead of
updateButton.IsEnabled = false;
What happens if you were instead to change the order of your events from:
updateButton.IsEnabled = false;
startWorkThread();
To
startWorkThread();
updateButton.IsEnabled = false;
Let me know how this goes.
What it looks like is that you are starting your thread then immediatly enabling your button before your thread has finished. You would be better off using a BackgroundWorker and enable your Button in the RunWorkerCompleted Event. Though you can do something similar by enabling your button using a BeginInvoke at the end of your Process.
public void doWork()
{
System.Threading.Thread.Sleep(10000); //Simulating your Process
Dispatcher.BeginInvoke(new System.Threading.ThreadStart(delegate() { updateButton.IsEnabled = true; }), System.Windows.Threading.DispatcherPriority.Background);
}
Example with BackgroundWorker
using System.ComponentModel;
public partial class MainWindow : Window
{
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
updateButton.IsEnabled = true;
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(10000); //Simulating your work
}
private void startWorkThread()
{
bgw.RunWorkerAsync();
}
private void updateButton_Click(object sender, RoutedEventArgs e)
{
if (bgw.IsBusy != true)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
}