I create a simple Windows Form with a text box, and Set button and a Toggle button. When I click the Toggle button, a thread is created setting text to the text box repeatedly. When I click the button again, the thread stops. When I click the Set button, a text is set to the text box once. Deadlock occurs if I do the following:
Run the app (in debug mode).
Click the Toggle button to let text run in the text box.
Click Set button. -> Deadlock occurs in this step.
Can you explain why and how deadlock occurs in this situation? How to avoid it?
Below is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace DeadLockTest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace DeadLockTest
{
public class Form1 : Form
{
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private int counter;
private Thread thread;
private bool cancelRequested;
private string content;
private object lockKey = new object();
public Form1()
{
InitializeComponent();
}
protected void UpdateContent()
{
this.textBox1.Text = this.content;
}
protected void InvokeUpdateContent()
{
lock (this.lockKey)
{
if (InvokeRequired)
{
Invoke(new Action(UpdateContent));
}
else
{
UpdateContent();
}
}
}
protected void SetText(string text)
{
this.content = text;
InvokeUpdateContent();
}
protected void StressTest()
{
int localCounter = 0;
while (!this.cancelRequested)
{
SetText(string.Format("{0}", localCounter++));
}
this.cancelRequested = false;
this.thread = null;
}
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(12, 12);
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.Size = new System.Drawing.Size(260, 20);
this.textBox1.TabIndex = 0;
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 38);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 1;
this.button1.Text = "Set";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(93, 38);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 2;
this.button2.Text = "Toggle";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 262);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.textBox1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
private void button1_Click(object sender, EventArgs e)
{
SetText(string.Format("{0}", this.counter++));
}
private void button2_Click(object sender, EventArgs e)
{
if (this.thread == null)
{
this.thread = new Thread(new ThreadStart(StressTest));
thread.Start();
}
else
{
this.cancelRequested = true;
}
}
}
}
Can you explain why and how deadlock occurs in this situation?
Sure...the deadlock occurs because of the lock()/Invoke() combination.
While the secondary thread is running and updating, it obtains a lock on the object. Then the secondary thread calls Invoke(), which is a synchronous call. It is key to realize that the secondary thread actually waits for the main UI thread to update the TextBox before continuing.
When you click the Set button, it attempts to update but must wait for the lock to be released by the secondary thread. At this point the main UI actually stops and freezes at the lock() line waiting for the secondary thread to release the lock. While waiting for the lock to be released, the main UI thread cannot process any messages.
But what is the secondary thread doing? It currently has the lock and is waiting for the main UI thread to service its synchronous Invoke() call. Since the main UI thread is waiting for the lock to be released, though, it cannot service any requests (including Invoke() requests) and bam...DEADLOCK! They are both waiting for each other.
How to avoid it?
Never use lock() from the main UI thread. In some scenarios, switching from Invoke() to BeginInvoke() can solve the problem since BeginInvoke() is asynchronous.
Related
I have a form Menu that won't fully dispose. Below is the complete form code. It's part of a larger system so other forms open and close before Menu is first opened.
There's a form timer that fires every second and prints whether the form is disposed or not. There's a button that opens another form, Search, and closes Menu. Search also has a timer that prints whether it is disposed or not.
When Menu opens, the debug output is as expected
*********** (in main menu): Disposed False
*********** (in main menu): Disposed False
When I click, I get timer ticks for both Menu and Search
*********** (in main): Disposed True
*************** (in search) Disposed False
It shows that the first instance of Menu is disposed but obviously the timer is still running. When I exit Search and Main is opened, there are now two Main timers running
*********** (in main): Disposed True
*********** (in main): Disposed False
I can keep doing this (click to open Search and exit) and the number of Main timers running keeps increasing. I'm perplexed. Here's the code for Main
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Diagnostics;
namespace Gui
{
public partial class Menu : Form
{
private System.Windows.Forms.Timer timer1;
private Button button1;
private IContainer components;
public Menu()
{
InitializeComponent();
}
private void Menu_Load(object sender, EventArgs e)
{
}
private void timer1_Tick(object sender, EventArgs e)
{
Debug.Print("*********** (in main): Disposed {0}", IsDisposed);
}
private void button1_Click(object sender, EventArgs e)
{
var search = new Search();
search.Show();
Close();
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// button1
//
this.button1.Location = new System.Drawing.Point(11, 17);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(125, 32);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Menu
//
this.ClientSize = new System.Drawing.Size(282, 253);
this.Controls.Add(this.button1);
this.Name = "Menu";
this.Load += new System.EventHandler(this.Menu_Load);
this.ResumeLayout(false);
}
}
}
this.timer1 = new System.Windows.Forms.Timer(this.components);
It looks like you copy/pasted the content of a Designer.cs file of a form class. The InitializeComponent() method is certainly boilerplate. But you didn't do it right, you forgot to actually use the this.components member. Which exists for only one reason, disposing any components that the form class uses. Like timer1. It is automatic for any controls you drop on the form, they can be found back through the form's Controls member but components need extra help.
So don't just copy/paste InitializeComponent(), you must copy/paste the Dispose() method as well:
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
And the timer now stops ticking when you close the form.
Anyone knows how to prevent app to be terminated when unhandled exception occurred from another thread? Seems doing AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException is only to capture when the error occurred but it won't handle it -- I mean it won't clear the error so the error keep continues.
Here's my workaround :
using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
class ErrorForm : Form {
private Button button2;
Thread newThread = null;
private ErrorForm() {
InitializeComponent();
}
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
public static void Main(string[] args) {
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Application.Run(new ErrorForm());
}
// Start a new thread, separate from Windows Forms, that will throw an exception.
private void button2_Click(object sender, EventArgs e) {
newThread = new Thread(() => {
throw new Exception("Aha!");
});
newThread.Start();
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
MessageBox.Show("CurrentDomain_UnhandledException");
}
private void InitializeComponent() {
this.button2 = new Button();
this.SuspendLayout();
//
// button2
//
this.button2.Location = new System.Drawing.Point(12, 12);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(260, 23);
this.button2.TabIndex = 1;
this.button2.Text = "Make Error";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += this.button2_Click;
//
// ErrorHandlerForm
//
this.ClientSize = new System.Drawing.Size(284, 52);
this.Controls.Add(this.button2);
this.Name = "ErrorHandlerForm";
this.ResumeLayout(false);
}
}
}
When I run this code and press button2, then CurrentDomain_UnhandledException invoked as expected, but next it goes back again to throw new Exception("Aha!");. The best thing I can do is to kill the process in method CurrentDomain_UnhandledException like this:
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
MessageBox.Show("CurrentDomain_UnhandledException");
if (e.IsTerminating) {
Process.GetCurrentProcess().Kill();
}
}
But it's not acceptable in my scenario of app. I want to the app (main form) kept live and this error smoothly handled. And I don't want to replace my logic done by Thread with BackgroundWorker. Is that possible? Any good solution for this?
Thank you in advance!
I have some code in my main form which loads another form as soon as the project launches:
private void Form1_Load(object sender, EventArgs e)
{
Updating upd = new Updating();
upd.Show();
}
This new form (currently) just contains this code:
private void Updating_Load(object sender, EventArgs e)
{
this.BackgroundImage = Properties.Resources.updating1;
Stopwatch dw = new Stopwatch();
dw.Start();
bool qsdf = true;
while (qsdf)
{
if (dw.ElapsedMilliseconds >= 3000) qsdf = false; dw.Stop();
}
this.BackgroundImage = Properties.Resources.updating2;
Stopwatch sw = new Stopwatch();
sw.Start();
qsdf = true;
while (qsdf)
{
if (sw.ElapsedMilliseconds >= 3000) qsdf = false; sw.Stop();
}
this.Close();
Now, for some reason, the form being called (form called Updating) doesn't show at all. All that currently happens is I get nothing at all for 6 seconds (as noted by the stopwatch) before I get my main form to show, not seeing the Updating form even once.
Note: this is the code in my Updating.Designer.cs which draws all the components:
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackgroundImage = global::BossCraftLauncher.Properties.Resources.updating1;
this.ClientSize = new System.Drawing.Size(300, 100);
this.ControlBox = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "Updating";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "BCL Update";
this.Load += new System.EventHandler(this.Updating_Load);
this.ResumeLayout(false);
Your loop is blocking the UI thread. As the load event fires.. your loop stops it from continuing it's message queue.
The easiest option for you (as you're using WinForms) is to use a Timer control and put your code in the Tick event.
Why are you closing the Updating form on the updating_Load?? "this.Close();"
If you write the close() function in the formload,after loading the screen the form will be closed.
i want when i click on button1 on form1 to call button2 that is on form2 and execute the code that is under button2 event.
The following code won't work:
button2.PerformClick();
The error i get is "button2 does not exist in current context", so i tried to set modifiers to public, also click event to set on public void... no luck.
form2.button2.PerformClick(); also doesn't work.
You should put the code that you want to call into a public method on Form2 and then call that method from form1.
If you need a specific instance of form2 to call the method from then you could store the Handle property from form2 somewhere and then get the appropriate form as follows.
var myForm = Form.FromHandle(myForm2Handle);
myForm.MyPublicMethod();
You could then call this from the Button1 click event.
You've got an architecture problem if you're reaching between forms and executing button click event code. You should really have an event system in place for this.
I'd suggest that Form2 would have a listener set up for an event on Form1:
public class Form2{
public Form2{
// get your form instance
Form1 MyForm1Instance = new Form1();
// hook up the event
MyForm1Instance.SomeEvent += new EventHandler(MyHandler);
}
public void MyHandler(){
// handle event here; run your button_click code or whatever
}
}
...and Form1 would simply need to fire "SomeEvent" when you click the appropriate button.
try this and see if it works, it works for me.
in the first form create your method
public void DoSomething(parameters)
{
//code to handle those parameters
}
in the calling or second form use this in the event you want to call form1 from
Form1 f1 = new Form1();
try
{
f1.DoSomething(arguments);
}
catch (Exception)
{
catch the exceptions
}
f1.Show();
comment if it works for u and mark it as an answer.
I think your problem is that you are trying to call instance method without object creation. Here is sample code to demostrate to you possible way of method calling:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new TestForm1());
}
}
public partial class TestForm1 : Form
{
private System.Windows.Forms.Button button1;
public void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Hello! Im TestForm1 and Im going to call TestForm2's code!");
// You must create TestForm2 because of button1_Click is not a static method!!!
TestForm2 form2 = new TestForm2();
form2.button1_Click(this, new EventArgs());
}
public TestForm1()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(88, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
this.Controls.Add(this.button1);
this.Name = "TestForm1";
this.Text = "TestForm1";
this.ResumeLayout(false);
}
}
public partial class TestForm2 : Form
{
private System.Windows.Forms.Button button1;
public void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Hello! Im TestForm2");
}
public TestForm2()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(88, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
this.Controls.Add(this.button1);
this.Name = "TestForm2";
this.Text = "TestForm2";
this.ResumeLayout(false);
}
}
}
Cannot access a disposed object
Anyone know why I would get this error "Cannot access a disposed object" on this line of code
Invoke(new UpdateTimerDelegate(UpdateTimer), new object[] { null });
Hit start over then either accept / reject / or close the form -- seems to happen when closing the form
the error happens when you bring the form up again
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace TimerTester
{
public class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void StartOverBtn_Click(object sender, EventArgs e)
{
ShowForm2();
}
private void Form1_Load(object sender, EventArgs e)
{
ShowForm2();
}
private void ShowForm2()
{
Form2 f2;
f2 = new Form2(10000);
f2.Owner = this;
switch (f2.ShowDialog())
{
case DialogResult.Yes:
break;
case DialogResult.No:
break;
default:
break;
}
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.StartOverBtn = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// StartOverBtn
//
this.StartOverBtn.Location = new System.Drawing.Point(33, 33);
this.StartOverBtn.Name = "StartOverBtn";
this.StartOverBtn.Size = new System.Drawing.Size(75, 23);
this.StartOverBtn.TabIndex = 0;
this.StartOverBtn.Text = "Start Over";
this.StartOverBtn.UseVisualStyleBackColor = true;
this.StartOverBtn.Click += new System.EventHandler(this.StartOverBtn_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.StartOverBtn);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button StartOverBtn;
}
}
form2
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
namespace TimerTester
{
public class Form2 : Form
{
public Form2(int timeoutSecondsForAcceptCall)
{
this.timeoutSecondsForAcceptCall = timeoutSecondsForAcceptCall;
InitializeComponent();
tUpdateTimer = new System.Threading.Timer(new System.Threading.TimerCallback(UpdateTimer), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
}
DateTime dtCreate = DateTime.Now;
int timeoutSecondsForAcceptCall = 99999;
System.Threading.Timer tUpdateTimer;
private delegate void UpdateTimerDelegate(object obj);
void UpdateTimer(object obj)
{
if (InvokeRequired == true)
{
Invoke(new UpdateTimerDelegate(UpdateTimer), new object[] { null });
}
else
{
TimeSpan ts = DateTime.Now - dtCreate;
if (ts.TotalSeconds > timeoutSecondsForAcceptCall)
{
DialogResult = DialogResult.Cancel;
return;
}
TimeSpan timeleft = TimeSpan.FromSeconds(timeoutSecondsForAcceptCall) - ts;
label1.Text = FormatTimeSpan(timeleft);
}
}
private string FormatTimeSpan(TimeSpan ts)
{
return ((int)ts.TotalMinutes).ToString().PadLeft(2, '0') + ":" + ((int)ts.Seconds).ToString().PadLeft(2, '0');
}
private void btnAccept_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Yes;
}
private void btnReject_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.No;
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnAccept = new System.Windows.Forms.Button();
this.btnReject = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// btnAccept
//
this.btnAccept.Location = new System.Drawing.Point(31, 30);
this.btnAccept.Name = "btnAccept";
this.btnAccept.Size = new System.Drawing.Size(75, 23);
this.btnAccept.TabIndex = 0;
this.btnAccept.Text = "Accept";
this.btnAccept.UseVisualStyleBackColor = true;
this.btnAccept.Click += new System.EventHandler(this.btnAccept_Click);
//
// btnReject
//
this.btnReject.Location = new System.Drawing.Point(140, 29);
this.btnReject.Name = "btnReject";
this.btnReject.Size = new System.Drawing.Size(75, 23);
this.btnReject.TabIndex = 1;
this.btnReject.Text = "Reject";
this.btnReject.UseVisualStyleBackColor = true;
this.btnReject.Click += new System.EventHandler(this.btnReject_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(31, 80);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 2;
this.label1.Text = "label1";
//
// Form2
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(240, 117);
this.Controls.Add(this.label1);
this.Controls.Add(this.btnReject);
this.Controls.Add(this.btnAccept);
this.Name = "Form2";
this.Text = "Form2";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btnAccept;
private System.Windows.Forms.Button btnReject;
private System.Windows.Forms.Label label1;
}
}
Your form is disposed by the time invoke() message is processed by the message loop. I donot think so there is a way to avoid it. You can put a check e.g
if(!yourForm.IsDisposed)
yourForm.Invoke(...)
But that may not work sometime as the invoke is posted to UI thread and by the time it is process form may already be disposed and you will still get that exception. You have to fix your program in a way to avoid this situation by making sure invoke is not posted on a disposed or disposing form. This is a fatal error and your program will exit after this. I think suppressing it via try{}catch{} will not work as framework already start to unload when catch block is executed.
You can fix the problem in two ways.
1. Just drop a timer component from toolbox i.e. System.Windows.Forms.Timer and use that.
Designer will automatically add it to component collection and dispose it before form is disposed.
2. Manually over OnDispose(bool disposing) in your form and perform
if(disposing)
yourTimer.Dispose()
So now your timer is dispose before your form so no subsequent invoke() will be posted to your form after its disposed.
You see this error, because your System.Threading.Timer is not component of form. Thus it doesn't get disposed when form is closed. I'd recommend you to use System.Windows.Forms.Timer instead. Because this is a component, which you can place on your Form and it will be disposed with other form's components (just drag it from toolbox in designer).
public class Form2 : Form
{
DateTime dtCreate = DateTime.Now;
int timeoutSecondsForAcceptCall = 99999;
public Form2(int timeoutSecondsForAcceptCall)
{
this.timeoutSecondsForAcceptCall = timeoutSecondsForAcceptCall;
InitializeComponent();
timer1.Interval = 1000;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
TimeSpan ts = DateTime.Now - dtCreate;
if (ts.TotalSeconds > timeoutSecondsForAcceptCall)
{
DialogResult = DialogResult.Cancel;
return;
}
TimeSpan timeleft = TimeSpan.FromSeconds(timeoutSecondsForAcceptCall) - ts;
label1.Text = FormatTimeSpan(timeleft);
}
// other code
}
BTW another benefit of using System.Windows.Forms.Timer is that it's Tick event handler executed on UI thread. Thus you don't need to verify if invoke required.
I've had this problem before. There are a couple things you should be doing.
1) When you close your program, you should be removing handlers and/or shutting down timers or other code that will run as the program is exiting. You are still left with the problem of how many timer handlers will still run after shutting down the timer. One possible solution is to set a "stop running" flag after you turn off the Timer. Check the flag in the handler and don't run the code if it is set. The flag could be a bool marked as volatile or a long using Interlocked to avoid concurrency issues, especially if your timer is really short.
2) Wrap a try/catch around your Invoke statement, also put one in the GUI editing code (the part where InvokeRequired == false).