I have a sub which starts one of two timers (depending on 'zone' condition). This sub called 'CheckAndActivateRelays' is itself called by a Serial Port _DataReceived event. I am inserting break points to help me troubleshoot and am seeing that the tmrSoundSirensAfterDelay.Start() line is being executed successfully with the status of the timer even changing to enabled. However the associated Tick event never executes any of the code contained within it.
If I do the same thing by calling the sub from within button24's click event, it works perfectly. Everything is on the same Form with no threaded processes.
Anyone? Thanks
private void checkAndActivateRelays(int zoneNumber)
{
if (globalFullAlarmSet || globalNightAlarmSet || globalDoorsAlarmSet)
{
if (zoneNumber == 1) //Entry zone
{
//kick off a timer after delay specified in Settings1 file,
if (Settings1.Default.alarmSirenDurationInMinutes != 0)
{
//activates the relays if global alarm flags are still set to true
//(i.e. user has not entered code in time)
globalAlarmEntryDurationTicks = 0;
tmrSoundSirensAfterDelay.Start();
}
}
else //If any other zone is activated during alarm set condition
{
if (Settings1.Default.alarmSirenDurationInMinutes != 0)
{
//Output to relays 1 & 2
spIOCard.Write("~out10=1~");
spIOCard.Write("~out11=1~");
//then close after duration from Settings1 file
globalAlarmSirenDurationTicks = 0;
tmrSoundSirens.Start();
}
}
}
}
private void tmrSoundSirensAfterDelay_Tick(object sender, EventArgs e)
{
globalAlarmEntryDurationTicks = globalAlarmEntryDurationTicks + 1;
if (globalAlarmEntryDurationTicks == Settings1.Default.alarmEntryDelayInSeconds) //Value from Settings1 file
{
spIOCard.Write("~out10=1~");
spIOCard.Write("~out11=1~");
globalAlarmEntryDurationTicks = 0;
tmrSoundSirensAfterDelay.Stop();
tmrSoundSirens.Start();
}
}
private void tmrSoundSirens_Tick(object sender, EventArgs e)
{
globalAlarmSirenDurationTicks = globalAlarmSirenDurationTicks + 1;
if (globalAlarmSirenDurationTicks == (Settings1.Default.alarmSirenDurationInMinutes * 5)) //*60 Value from Settings1 file
{
spIOCard.Write("~out10=0~");
spIOCard.Write("~out11=0~");
globalAlarmSirenDurationTicks = 0;
tmrSoundSirens.Stop();
}
}
private void button24_Click(object sender, EventArgs e)
{
globalFullAlarmSet = true;
checkAndActivateRelays(1);
}
Serial Port Data Received Code:
private void spIO_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
RxString = spIOCard.ReadExisting();
if (RxString == "~in00=1~")
{
checkAndActivateRelays(1);
button10.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in00=0~")
{
button10.BackColor = System.Drawing.Color.LightGray;
}
if (RxString == "~in01=1~")
{
checkAndActivateRelays(2);
button11.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in01=0~")
{
button11.BackColor = System.Drawing.Color.LightGray;
}
if (RxString == "~in02=1~")
{
button12.BackColor = System.Drawing.Color.Red;
}
if (RxString == "~in02=0~")
{
button12.BackColor = System.Drawing.Color.LightGray;
}
}
Something to think about since you are using the DataReceivedEvent. According to MSDN it is raised on a secondary thread. This is probably causing your issue.
The DataReceived event is raised on a secondary thread when data is
received from the SerialPort object. Because this event is raised on a
secondary thread, and not the main thread, attempting to modify some
elements in the main thread, such as UI elements, could raise a
threading exception. If it is necessary to modify elements in the main
Form or Control, post change requests back using Invoke, which will do
the work on the proper thread.
Since calling Start() is not the problem the timer setup is where you need to look. Make sure you handle the tick event AND set an interval.
myTimer.Tick += new EventHandler(TimerEventProcessor);
// Sets the timer interval to 5 seconds.
myTimer.Interval = 5000;
myTimer.Start();
The key here is that you are doing this in the SerialPort DataReceived event. This event is fired on a separate thread. Thats important because you probably registered for the Tick event on the main thread, but you start the timer on a different one. You'll need to register the Tick event in the checkAndActivateRelays function. Then it should be happy.
The DataReceived event is raised on a secondary thread when data is received from the SerialPort object. Because this event is raised on a secondary thread, and not the main thread, attempting to modify some elements in the main thread, such as UI elements, could raise a threading exception. If it is necessary to modify elements in the main Form or Control, post change requests back using Invoke, which will do the work on the proper thread.
Related
I have a timer of 1 second in C#, with a while sequence in it. My question is if the while sequence is not finished before 1 second, will the timer tick, and restart the while from the beginning?
The part of the code is below, and what it does is that it cycles through the selected objects and changes something. So, if there are a lot of objects selected and I need more than 1 second to change them, will they all be changed?
P.S. I actually want the loop to be broken; a large number of objects will be selected only by mistake, but I just want to be sure that I avoid this possibility. :)
private void timer1_Tick(object sender, EventArgs e)
{
TSM.ModelObjectEnumerator myEnum = null;
myEnum = new TSM.UI.ModelObjectSelector().GetSelectedObjects();
while (myEnum.MoveNext())
{
if (myEnum.Current != null)
{....}
}
}
Yes, timer ticks can happen concurrently. This means that your timer must be thread-safe.
Except for the UI timer classes (WinForms/WPF). Their tick functions run on the UI thread. With DoEvents you can cause reentrancy even there which is another reason to avoid DoEvents.
From the name of the handler I assume you are using System.Windows.Forms.Timer which is single-threaded. That means the Tick event will fire after the previous one has ended. To break the loop, you will have to execute the code in another thread an use an exit condition.
This is how I usually do it:
private bool running;
private bool restart;
private void DoWork(object item)
{
running = true;
TSM.ModelObjectEnumerator myEnum = null;
myEnum = new TSM.UI.ModelObjectSelector().GetSelectedObjects();
while (myEnum.MoveNext() && !restart)
{
//do your stuff
if (myEnum.Current != null) {....}
}
if(restart)
{
restart = false;
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (running)
restart = true;
else
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
}
A workaround would be to disable the timer at the top of the while event, and re-enable it as you exit the while event.
The while loop will not be broken because the timer has ticked again. But in any case, your best bet would be to disable the timer at the beginning of the event handler, and re-enable it again at the end.
You could always try something similar to this instead, that way you void having multiple timers tick over and kick off processes. Written in Notepad so please excuse any massive spelling mistakes
private Timer _systemTimer = null;
public MyApp()
{
_systemTimer = new Timer("how ever you set your 1 second);
// Create your event handler for when it ticks over
_systemTimer.Elapsed += new ElapsedEventHandler(systemTimerElapsed);
}
protected void systemTimerElapsed(object sender, ElapsedEventArgs e)
{
_systemTimer.Stop();
//Do what you need to do
_systemTimer.Start();
//This way if it takes longer than a second it won't matter, another time won't kick off until the previous job is done
}
I will make it very easy for you;use Thread.Sleep() in another background thread and it is done!
If you know when are you finish than just use AutoResetEvent to keep threads in sync.
If you do not have any control on the update no callback , time is unknown I suggest to increase your timer interval!
var thread = new Thread((ThreadStart)delegate
{
While(true)
{
TSM.ModelObjectEnumerator myEnum = null;
myEnum = new TSM.UI.ModelObjectSelector().GetSelectedObjects();
while (myEnum.MoveNext())
{
if (myEnum.Current != null)
{....}
}
Thread.Sleep(1000);
}
}
thread.Start();
Get each char from string from txtString and write on label one by one char with timerControl
int g = 0;
private void timerString_Tick(object sender, EventArgs e)
{
string a = txtString.Text;
int em = txtString.TextLength;
if (g < em)
{
lblString.Text = lblString.Text + a[g];
g++;
}
else timerString.Stop();
}
Call from
private void btnStringStart_Click(object sender, EventArgs e)
{
timerString.Start();
lblString.Text = "";
}
I've spent the last few days looking at the DispatcherTimer and I still can't wrap my head around some stuff. Here's what I understand so far,
the tick event will not occur twice at the same time link
there is no need to worry about the owner thread(s) of the objects
since the dispatcher timer automatically performs all the work in
the UI thread
the timing of the ticks may not be very accurate since the ticks are essentially executed from a queue
Now what I'm not clear about is the order of the code being executed if there is another event which runs in between a tick event. I've a test WPF application which uses a DispatcherTimer whose tick event performs 2 functions. firstStep() and secondStep() in sequence.
The firstStep()sets a variable to null while secondStep() sets it to a value that is not null. After setting the value, secondStep() will begin a storyboard which has a Completed event, which attempts to access this variable.
So my question is, is it possible for the Completed event to come in between the firstStep() and secondStep() function if we keep the timer running? I've written a test application and it seems to be that case, eventually we will reach a state where the variable is null when the Completed event gets executed. But I don't understand how that can happen, since firstStep() and secondStep() get executed in sequence, there should be no way the Completed event can be executed between the 2 functions (or I am wrong here). Does the UI thread execute the tick and the Completed event in parallel?
Can someone explain to me in detail how the UI thread executes events such as the example's storyboard completed event and dispatcherTimer's ticks in sequence? Thanks for reading, your comments are very much appreciated I'm trying very hard to get my head around this. The following is the test code I used, it will eventually throw an error after running for a while.
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
storyBoardTest = new Storyboard();
storyBoardTest.Completed += new EventHandler(storyBoardTest_Completed);
DoubleAnimation animation = new DoubleAnimation(1, 0.9, new Duration(TimeSpan.FromSeconds(1)));
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation, new PropertyPath(UIElement.OpacityProperty));
storyBoardTest.Children.Add(animation);
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = TimeSpan.FromMilliseconds(500);
dt.Tick += new EventHandler(dt_Tick);
dt.Start();
}
private Window windowTest = null;
private Storyboard storyBoardTest = null;
void dt_Tick(object sender, EventArgs e)
{
firstStep();
secondStep();
}
private void firstStep()
{
windowTest = null;
}
private void secondStep()
{
windowTest = this;
storyBoardTest.Stop();
storyBoardTest.Begin(this);
}
void storyBoardTest_Completed(object sender, EventArgs e)
{
//Attempt to access object throws null error. Why?
windowTest.Title = "test";
windowTest = null;
}
}
CallStack:
WpfApplication1.exe!WpfApplication1.Window1.storyBoardTest_Completed(object sender = {System.Windows.Media.Animation.ClockGroup}, System.EventArgs e = null) Line 63 C#
PresentationCore.dll!System.Windows.Media.Animation.Clock.FireEvent(System.Windows.EventPrivateKey key) + 0x5b bytes
PresentationCore.dll!System.Windows.Media.Animation.Clock.RaiseAccumulatedEvents() + 0x160 bytes
PresentationCore.dll!System.Windows.Media.Animation.TimeManager.RaiseEnqueuedEvents() + 0x60 bytes
PresentationCore.dll!System.Windows.Media.Animation.TimeManager.Tick() + 0x28a bytes
PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandlerCore(object resizedCompositionTarget) + 0xbc bytes
PresentationCore.dll!System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(object resizedCompositionTarget) + 0x9d bytes
Every 500 milliseconds you are starting a Storyboard that runs for one second. This will inevitably lead to two consecutive Completed events without an intermediate Tick event.
Therefore you have to check if windowTest is already null in your Completed handler :
void storyBoardTest_Completed(object sender, EventArgs e)
{
if (windowTest != null)
{
windowTest.Title = "test";
windowTest = null;
}
}
Even if the Storyboard would run for less than 500 milliseconds there would be problem. As Storyboard.Completed events are appended to the Dispatcher queue in the same way as DispatcherTimer.Tick events and the timings of both DispatcherTimer and Storyboard are not exact, the execution order of the two event handlers is not reliable. Hence two Completed events may occur without an intermediate Tick event.
You may add some trace output to see that both handlers run in the same thread.
void dt_Tick(object sender, EventArgs e)
{
Trace.TraceInformation("Tick: {0}", Thread.CurrentThread.ManagedThreadId);
...
}
void storyBoardTest_Completed(object sender, EventArgs e)
{
Trace.TraceInformation("Completed: {0}", Thread.CurrentThread.ManagedThreadId);
...
}
I need to implement an auto logout feature in C#. Previously i have asked a similiar question before and i managed to implement it using the System.Windows.Forms.Timer . But right now i have a additional requirement apart from resetting the timer when the user move the mouse or enters a key i also need to reset the timer when a new message is received via the serial port ( DataReceived event handler ).
serialPort.DataReceived += port_DataRecieved;
I need to include the reset function in a portion of the port_DataRecieved function. I cannot simply add another delegate method to the serialPort.DataReceived which will perform the reset as the serialPort.DataReceived will received a lot of other messages that i am not interested in. I want to perform a reset when the message that i am interested in arrives. And i know where to put the reset feature. The issue is that the timer does not reset in port_DataRecieved method. And i cannot achieve the desired result using the System.Threading.Timer. Anyone can guide me or provide some suggestion on this issue ? Any help provided will be greatly apperciated.
public partial class Form1: Form
{
private System.Windows.Forms.Timer sessionTimer = new System.Windows.Forms.Timer();
public Form1()
{
initialiseTimer();
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
try
{
serialPort= (SerialPort)sender;
str = serialPort.ReadExisting();
string[] split = str.Split(Convert.ToChar(10));
for (int i = 1; i < split.Length; i++)
{
str = split[i];
if (split[i].StartsWith("+CMTI:"))
{
sessionTimer.Stop();
sessionTimer.Start();
//Other codes
}
}
}
catch (Exception)
{
MessageBox.Show("Error processing received commands !", "CONNECTION ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
sendRecPort.Close();
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void initialiseTimer()
{
sessionTimer.Interval = (5 * 60 * 1000);
sessionTimer.Tick += new EventHandler(logOutUser);
sessionTimer.Stop();
sessionTimer.Start();
}
private void logOutUser(object sender, EventArgs e)
{
// logout the user
this.Hide();
//Open up the login Form
login.Show();
}
}
Your problem is that the the DataReceived event is being executed on a thread other than the UI thread. You're trying to modify the timer (a UI object) from a non-UI thread. This typically throws an exception, but it's possible that the method that issues the DataReceived event is swallowing that exception.
From the documentation for the DataReceived event:
The DataReceived event is raised on a
secondary thread when data is received
from the SerialPort object. Because
this event is raised on a secondary
thread, and not the main thread,
attempting to modify some elements in
the main thread, such as UI elements,
could raise a threading exception. If
it is necessary to modify elements in
the main Form or Control, post change
requests back using Invoke, which will
do the work on the proper thread.
You need to synchronize with the UI thread to set the timer.
void ResetTimer()
{
sessionTimer.Stop();
sessionTimer.Start();
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
//Other codes
this.Invoke((MethodInvoker)delegate { ResetTimer(); });
//Other codes
}
I need to include the reset function in a portion of the port_DataReceived function.
Ok. Gotcha.
I cannot simply add another delegate method to the serialPort.DataReceived which will perform the reset as the serialPort.DataReceived will receive a lot of other messages that I am not interested in.
Ok, but I thought you said:
I want to perform a reset when the message that I am interested in arrives.
So you either have to listen to that DataReceived method, or you won't know when that message arrives.
I'm confused. What is it you want to do? Magic?
if (dataReceived == "someValue1")
{
//action if matches "someValue1"
}
else if (dataReceived.Contains("someValue2"))
{
// action if contains "someValue2"
}
else if (dataReceived.IndexOf("someValue3") != -1 )
{
// action if contains "someValue3"
}
else if (dataReceived == "someValue4")
{
// action if matches "someValue4"
}
else
{
// default action
}
I have a form with 2 comboboxes on it. And I want to fill combobox2.DataSource based on combobox1.Text and combobox2.Text (I assume that the user has completed input in combobox1 and is in the middle of inputting in combobox2). So I have an event handler for combobox2 like this:
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
As far as building DataSource is time-consuming process (it creates a request to database and executes it) I decided that it's better to perform it in another process using BackgroundWorker. So there's a scenario when cmbDataSourceExtractor hasn't completed its work and the user types one more symbol. In this case I get an exception on this line
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); about that BackgroundWorker is busy and cannot perform several actions in the same time.
How to get rid of this exception?
CancelAsync doesn't actually abort your thread or anything like that. It sends a message to the worker thread that work should be cancelled via BackgroundWorker.CancellationPending. Your DoWork delegate that is being run in the background must periodically check this property and handle the cancellation itself.
The tricky part is that your DoWork delegate is probably blocking, meaning that the work you do on your DataSource must complete before you can do anything else (like check for CancellationPending). You may need to move your actual work to yet another async delegate (or maybe better yet, submit the work to the ThreadPool), and have your main worker thread poll until this inner worker thread triggers a wait state, OR it detects CancellationPending.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
If you add a loop between the CancelAsync() and the RunWorkerAsync() like so it will solve your problem
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
The while loop with the call to Application.DoEvents() will hault the execution of your new worker thread until the current one has properly cancelled, keep in mind you still need to handle the cancellation of your worker thread. With something like:
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
The Application.DoEvents() in the first code snippet will continue to process your GUI threads message queue so the even to cancel and update the cmbDataSourceExtractor.IsBusy property will still be processed (if you simply added a continue instead of Application.DoEvents() the loop would lock the GUI thread into a busy state and would not process the event to update the cmbDataSourceExtractor.IsBusy)
You will have to use a flag shared between the main thread and the BackgroundWorker, such as BackgroundWorker.CancellationPending. When you want the BackgroundWorker to exit, just set the flag using BackgroundWorker.CancelAsync().
MSDN has a sample: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx
MY example . DoWork is below:
DoLengthyWork();
//this is never executed
if(bgWorker.CancellationPending)
{
MessageBox.Show("Up to here? ...");
e.Cancel = true;
}
inside DoLenghtyWork :
public void DoLenghtyWork()
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
inside OtherStuff() :
public void OtherStuff()
{
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
What you want to do is modify both DoLenghtyWork and OtherStuff() so that they become:
public void DoLenghtyWork()
{
if(!bgWorker.CancellationPending)
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
public void OtherStuff()
{
if(!bgWorker.CancellationPending)
{
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
The problem is caused by the fact that cmbDataSourceExtractor.CancelAsync() is an asynchronous method, the Cancel operation has not yet completed when cmdDataSourceExtractor.RunWorkerAsync(...) exitst. You should wait for cmdDataSourceExtractor to complete before calling RunWorkerAsync again. How to do this is explained in this SO question.
My answer is a bit different because I've tried these methods but they didn't work. My code uses an extra class that checks for a Boolean flag in a public static class as the database values are read or where I prefer it just before an object is added to a List object or something as such. See the change in the code below. I added the ThreadWatcher.StopThread property. for this explation I'm nog going to reinstate the current thread because it's not your issue but that's as easy as setting the property to false before accessing the next thread...
private void combobox2_TextChanged(object sender, EventArgs e)
{
//Stop the thread here with this
ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped.
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
all fine
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
Now add the following class
public static class ThreadWatcher
{
public static bool StopThread { get; set; }
}
and in your class where you read the database
List<SomeObject>list = new List<SomeObject>();
...
if (!reader.IsDbNull(0))
something = reader.getString(0);
someobject = new someobject(something);
if (ThreadWatcher.StopThread == true)
break;
list.Add(something);
...
don't forget to use a finally block to properly close your database connection etc. Hope this helps! Please mark me up if you find it helpful.
In my case, I had to pool database for payment confirmation to come in and then update WPF UI.
Mechanism that spins up all the processes:
public void Execute(object parameter)
{
try
{
var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, "transactionRef");
Process.Start(new ProcessStartInfo(url));
ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true};
ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync();
}
catch (Exception e)
{
ViewModel.Log.Error("Failed to navigate to payments", e);
MessageBox.Show("Failed to navigate to payments");
}
}
Mechanism that does checking for completion:
private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(30000);
while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending)
{
Thread.Sleep(5000);
}
//Plug in pooling mechanism
this.AuthCode = GetAuthToken();
}
Mechanism that cancels if window gets closed:
private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e)
{
var context = DataContext as PaymentViewModel;
if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy)
context.UpdateUiWhenDoneWithPayment.CancelAsync();
}
I agree with guys. But sometimes you have to add more things.
IE
1) Add this worker.WorkerSupportsCancellation = true;
2) Add to you class some method to do the following things
public void KillMe()
{
worker.CancelAsync();
worker.Dispose();
worker = null;
GC.Collect();
}
So before close your application your have to call this method.
3) Probably you can Dispose, null all variables and timers which are inside of the BackgroundWorker.
I've been trying to get the logic right for my timer and backgroundworker thread. Granted I don't fully understand the whole system despite all my reading. the following are excerpts of code concerned:
My polling button :
private void pollStart_Click(object sender, EventArgs e)
{
tst_bgw = new BackgroundWorker();
//mandatory. Otherwise will throw an exception when calling ReportProgress method
tst_bgw.WorkerReportsProgress = true;
//mandatory. Otherwise we would get an InvalidOperationException when trying to cancel the operation
tst_bgw.WorkerSupportsCancellation = true;
tst_bgw.DoWork += tst_bgw_DoWork;
tst_bgw.ProgressChanged += tst_bgw_ProgressChanged;
tst_bgw.RunWorkerCompleted += tst_bgw_RunWorkerCompleted;
tst_bgw.RunWorkerAsync();
}
which I think is right so far
my Background worker thread:
private void tst_bgw_DoWork(object source, DoWorkEventArgs e)
{
m_timer = new System.Timers.Timer();
m_timer.Interval = 1000;
m_timer.Enabled = true;
m_timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
if (tst_bgw.CancellationPending)
{
e.Cancel = true;
return;
}
}
and the elapsed tier event code:
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
if (powerVal > 3250)
{
m_timer.Stop();
tst_bgw.CancelAsync();
}
else
{
string pow;
int progressVal = 100 - ((3250 - powerVal) / timerVal);
uiDelegateTest tstDel = new uiDelegateTest(recvMessage);// the recvMessage function takes a textbox as an argument and directs output from socket to it.
pow = construct_command("power", powerVal);
sData = Encoding.ASCII.GetBytes(pow);
if (active_connection)
try
{
m_sock.Send(sData);
Array.Clear(sData, 0, sData.Length);
tstDel(ref unit_Output);// Read somewhere that you can only modify UI elements in this method via delegate so I think this is OK.
m_sock.Send(time_out_command);
tstDel(ref unit_Output);
tst_bgw.ReportProgress(progressVal);
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
tst_bgw.ReportProgress(powerVal, progressVal);
powerVal = powerVal + pwrIncVal;
}
I'd just like to know a few other things; am I using the right timer (not that I think it should matter greatly but it was suggested that this might be the best timer for what I want to do) and canI really modify UI elements in the DoWork method only through delegates and if yes are there sepcial considerations to doing so.
Sorry about the long posting and thank you for your time.
There is lots wrong with this code.
1) You aren't disposing of your background worker. BackgroundWorkers must be disposed of after use. They are designed to be used as winforms components and would normally be added to a window via the designer. This will ensure it is created with the form and disposed of when the form is.
2) All you are doing in your dowork method is creating a new timer and running it. There is no point of doing this in a background worker because it will happen so quickly anyway.
3) You will recreate the timer every time you run the background worker again. But you aren't ever stopping or disposing of the old timer, you are just overwriting the member.
I recommend you get rid of the BackgroundWorker completely and just use a timer. Create the timer in the forms constructor and make sure you dispose of it in the forms dispose method. (Or use the designer to add it to the form). In the pollstart_click method just start the timer. (If you have a poll stop method, you can stop the timer in that)
You don't need both a BackgroundWorker and a Timer to accomplish your goal. From what you have posted it looks like you want to have the user click a button which starts a polling process that quits at a certian point.
Your polling model really suggests a timer would work just fine.
If you use a Timer I would Initialize the timer after the InitializeComponent() call with something like
private void InitializeTimer()
{
this.timer = new Timer();
int seconds = 1;
this.timer.Interval = 1000 * seconds; // 1000 * n where n == seconds
this.timer.Tick += new EventHandler(timer_Tick);
// don't start timer until user clicks Start
}
The button_click will simply
private void button_Click(object sender, EventArgs e)
{
this.timer.Start();
}
Then on the timer_Tick you will need to do your polling and you should be able to update your UI from there if the timer is on the UI thread like this
void timer_Tick(object sender, EventArgs e)
{
if( determineIfTimerShouldStop() )
{
this.timer.Stop();
}
else
{
// write a method to just get the power value from your socket
int powerValue = getPowerValue();
// set progressbar, label, etc with value from method above
}
}
However if the timer thread is not on the same thread as the UI you well get an exception while trying to update the UI. In that case you can use the Invoke that DataDink mentions and do something like this
void timer_Tick(object sender, EventArgs e)
{
if( determineIfTimerShouldStop() )
{
this.timer.Stop();
}
else
{
// write a method to just get the power value from your socket
int powerValue = getPowerValue();
// set a label with Invoke
mylabel.Invoke(
new MethodInvoker( delegate { mylabel.Text = "some string"; } )
);
}
}
Given the code you posted you really didn't need to do both a BackgroundWorker and a Timer, but I have had instances where I have used a BackgroundWorker to do work when a timer is called so that I could have a timer update UI periodically and have a manual button to Refresh the UI. But I wasn't updating my UI quite the way you are.
If you still have the need to do both, here is, roughly, how you can flow your app...
Create an
InitailizeBackgroundWorker() method
along with the InitializeTimer so you have
it already initalized before the
Timer fires.
Then set the Timer.Tick
to call the
BackgroundWorker.RunWorkerAsync()
Then you can do all the UI updates from within the RunWorkerAsync by
using the
BackgroundWorker.ReportProgress().