C# CrossThreadMessagingException.

Kilgore_Trout_Redux

Executive Member
Joined
Sep 20, 2006
Messages
7,498
Reaction score
347
Hi,

I'm busy boning up on my C# skills and wanted to write some code from scratch to help me get my head around delegates and events. The code looks ok but I'm having issues accessing the eventargs from my form in the form of a CrossThreadMessagingException. I know that I need to invoke the call somehow so that it is threadsafe but I'm not sure how to do that the way the code is structured.

The idea is that I am simulating a thermometer that sends readings of temperature and the date of the reading at five second intervals. The readings will be displayed on a form. I want this to be reusable so I don't want the Thermometer class to know anything about what is accessing the delegate.

Here is the code :

For the form :

------------

Code:
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private Thermometer _therm = new Thermometer();

        private void btnStartTempReading_Click(object sender, EventArgs e)
        {
            _therm.OnTempReading += new Thermometer.TemperatureHandler(ShowTemperatureMeasureMent);
            _therm.StartReading();
        }

        private void ShowTemperatureMeasureMent(Thermometer therm, ThermometerEventArgs e)
        {
            //string s = e.Temperature.ToString("00.00");
            lblTemp.Text = e.Temperature.ToString("00.00");
            lblTime.Text = e.TimeOfMeasurement.ToString();            
        }

        private void btnStopTempReading_Click(object sender, EventArgs e)
        {
            _therm.StopReading();
        }


    }

and the code for the Thermometer class :

Code:
    class ThermometerEventArgs : EventArgs
    {
        public double Temperature
        {
            get {
                Random rand = new Random();
                return (rand.NextDouble() * rand.Next(-50, 50));
                }
        }

        public DateTime TimeOfMeasurement
        {
            get { return DateTime.Now; }
        }
    }

    class Thermometer
    {
        #region events and delegates
        public event TemperatureHandler OnTempReading;
        public delegate void TemperatureHandler(Thermometer therm, ThermometerEventArgs e);
        #endregion

        private Timer _timer;

        public void StartReading()
        {

            //_state = true;
            if ((OnTempReading != null) && (_timer == null))
            {
                _timer = new Timer(delegate(object o) { OnTempReading(this, new ThermometerEventArgs()); }, null, 5000, 5000);
            }
        }

        public void StopReading()
        {
            _timer.Dispose();
        }

    }

Also, any other code review, comments, arsekickery for idiotic coding would be much appreciated. :o
 
The problem is that the Timer in your Thermometer class runs in a different thread than the GUI form.
WinForms does not like it when you change GUI controls from another thread.

To solve the problem (pseudo code here)
Code:
        private void ShowTemperatureMeasureMent(Thermometer therm, ThermometerEventArgs e)
        {
			if ( this.InvokeRequired )
			{
				this.Invoke( bla bla bla, new Thermometer.TemperatureHandler( ShowTemperatureMeasureMent ), etc etc etc );
			} else
			{
				//string s = e.Temperature.ToString("00.00");
				lblTemp.Text = e.Temperature.ToString("00.00");
				lblTime.Text = e.TimeOfMeasurement.ToString();            
			}
        }

So, go read up on Form.Invoke to get the details.

Hope this helps.
 
The problem is that the Timer in your Thermometer class runs in a different thread than the GUI form.
WinForms does not like it when you change GUI controls from another thread.

To solve the problem (pseudo code here)
Code:
        private void ShowTemperatureMeasureMent(Thermometer therm, ThermometerEventArgs e)
        {
			if ( this.InvokeRequired )
			{
				this.Invoke( bla bla bla, new Thermometer.TemperatureHandler( ShowTemperatureMeasureMent ), etc etc etc );
			} else
			{
				//string s = e.Temperature.ToString("00.00");
				lblTemp.Text = e.Temperature.ToString("00.00");
				lblTime.Text = e.TimeOfMeasurement.ToString();            
			}
        }

So, go read up on Form.Invoke to get the details.

Hope this helps.

The thing is that I understand that ShowTemperatureMeasureMent(Thermometer therm, ThermometerEventArgs e) is what is actually being run by the Thermometer class in

new Timer( delegate(object o) {OnTempReading(this, new ThermometerEventArgs()); }, null, 5000, 5000);

and that it needs to be invoked in a thread safe way but in the context of the code I've written though I'm not sure how exactly to do that. I've tried hunting solutions down but in conjuction with the anonymous delegate there I'm really not sure how it should happen :(
 
Sorted and the solution was really easy :
Code:
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(() => { ShowTemperatureMeasureMent(therm, e); }));
            }
            else
            {
                lblTemp.Text = e.Temperature.ToString("00.00");
                lblTime.Text = e.TimeOfMeasurement.ToString();

            }
 
Top
Sign up to the MyBroadband newsletter
X