0

I'm new to the coding game so I'm not too sure how to remedy this. My code pulls information from a thread that harvests data from a 9DOF sensor which comes out as three Euler angles. The data is then used to generate points from which a circle is made on screen with the drawing class. To works perfectly for a while, but eventually will always give this exception saying that there was an unhandled exception of type 'System.InvalidOperationException' that occurred in System.Drawing.dll with additional information saying that the object is currently in use elsewhere. After poking around a bit more and playing with what is drawn, I've come to the (possibly incorrect) conclusion that the thread is sending data faster than the main code can render the drawing. How can I prevent this? An excerpt of the code is below.

private void button3_Click(object sender, EventArgs e)
        {
            //single node test
            Nodes.NodesList.Add(new RazorIMU("COM7"));
            Nodes.NodesList[0].StartCollection('e');
            Nodes.NodesList[0].CapturedData += new RazorDataCaptured(Fusion_CapturedData);

public void Fusion_CapturedData(float[] data, float deltaT)
        {
            int centerX = (int)(300 + (5 / 3) * data[0] + 0.5);
            int centerY = (int)(300 + (5 / 3) * data[1] + 0.5);
            int endPointX = (int)(centerX + 25 * Math.Sin(Math.PI / 180 * data[2]) + 0.5);
            int endPointY = (int)(centerY + 25 * Math.Cos(Math.PI / 180 * data[2]) + 0.5);

            Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);

            using (Graphics g = Graphics.FromImage(bmp))
            {
                /*g.DrawLine(new Pen(Color.Yellow), 300, 0, 300, 600);
                g.DrawLine(new Pen(Color.Yellow), 0, 300, 600, 300);
                g.DrawLine(new Pen(Color.LightYellow), 150, 0, 150, 600);
                g.DrawLine(new Pen(Color.LightYellow), 0, 150, 600, 150);
                g.DrawLine(new Pen(Color.LightYellow), 450, 0, 450, 600);
                g.DrawLine(new Pen(Color.LightYellow), 0, 450, 600, 450);*/
                g.DrawEllipse(new Pen(Color.Green), centerX - 25, centerY - 25, 50, 50);
                g.DrawLine(new Pen(Color.Green), centerX, centerY, endPointX, endPointY);
            }

            pictureBox1.Image = bmp;
        }

This is the main code. The thread simply sends in information at the speed at which it is received, so I don't think I need to put that in here unless anyone says otherwise.

{ public delegate void RazorDataCaptured(float[] data, float deltaT);

/// <summary>
/// 
/// Object for Sparkfun's 9 Degrees of Freedom - Razor IMU
/// Product ID SEN-10736
///     https://www.sparkfun.com/products/10736
/// 
/// Running Sample Firmware
///     https://github.com/a1ronzo/SparkFun-9DOF-Razor-IMU-Test-Firmware
///     
/// </summary>
public class RazorIMU : IDisposable
{
    private SerialPort _com;
    private static byte[] TOGGLE_AUTORUN = new byte[] { 0x1A };

    public string Port { get; private set; }

    private Thread _updater;
    private string[] _parts;

    public event RazorDataCaptured CapturedData = delegate { };
    private float[] _data = new float[9];

    private static bool running = false;

    /// <summary>
    /// Create a new instance of a 9DOF Razor IMU
    /// </summary>
    /// <param name="portName">Serial port name. Ex: COM1.</param>
    public RazorIMU(string portName)
    {
        // Create and open the port connection.
        _com = new SerialPort(portName, 57600, Parity.None, 8, StopBits.One);
        _com.Open();
        Port = portName;
        // Set the IMU to automatically collect data if it has not done yet.
        //Thread.Sleep(3000);
        //_com.Write("#s00");
        //_com.DiscardInBuffer();
    }

    /// <summary>
    /// Start continuous collection of data.
    /// </summary>
    public void StartCollection(char dataType)
    {
        running = true;
        if (dataType == 'r') _updater = new Thread(new ThreadStart(ContinuousCollect));
        else if (dataType == 'q') _updater = new Thread(new ThreadStart(ContinuousCollectQuat));
        else if (dataType == 'e') _updater = new Thread(new ThreadStart(ContinuousCollectEuler));
        _updater.Start();
    }

    /// <summary>
    /// Stop continuous collect of data.
    /// </summary>
    public void StopCollection()
    {
        if (_updater != null)
        {
            running = false;
            _updater.Join();
            _updater = null;
        }
    }

    /// <summary>
    /// This method is extremely important. It continously updates the data array.
    /// Data is read and the change in time since the last read is calculated.
    /// The CapturedData event is triggered, sending the new data and the change in time.
    /// </summary>
    private void ContinuousCollect()
    {
        _com.WriteLine("#osr"); //Sets sensor output data to raw.
        _com.ReadLine(); //Discards first line if broken.
        while (running) //Static Boolean that controls whether or not to keep running.
        {
            ReadDataRaw();
            CapturedData(_data, 0.020F);
        }
    }
    private void ContinuousCollectQuat()
    {
        _com.WriteLine("#ot"); //Sets sensor output data to quaternions.
        _com.ReadLine(); //Discards first line if broken.
        while (running) //Static Boolean that controls whether or not to keep running.
        {
            ReadDataQuat();
            CapturedData(_data, 0.020F);
        }
    }
    private void ContinuousCollectEuler()
    {
        _com.WriteLine("#ob"); //Sets sensor output data to quaternions.
        _com.ReadLine(); //Discards first line if broken.
        while (running) //Static Boolean that controls whether or not to keep running.
        {
            ReadDataEuler();
            CapturedData(_data, 0.020F);
        }
    }

    /// <summary>
    /// Get a single sample of the 9DOF Razor IMU data. 
    /// <para>Format: [accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z,mag_x,mag_y,mag_z]</para>
    /// </summary>
    /// <param name="result">double array of length 9 required.</param>
    private void ReadDataRaw()
    {
        _parts = _com.ReadLine().Split(',');

        if (_parts.Length == 9)
            for (int i = 0; i < 9; i++)
                _data[i] = float.Parse(_parts[i]);
    }
    private void ReadDataQuat()
    {
        _parts = _com.ReadLine().Split(',');

        if (_parts.Length == 4)
            for (int i = 0; i < 4; i++)
                _data[i] = float.Parse(_parts[i]);
    }
        private void ReadDataEuler()
    {
        _parts = _com.ReadLine().Split(',');

        if (_parts.Length == 3)
            for (int i = 0; i < 3; i++)
                _data[i] = float.Parse(_parts[i]);
    }

    /// <summary>
    /// 
    /// </summary>
    public void Dispose()
    {
        StopCollection();
        if (_com != null) //Make sure _com exists before closing it.
            _com.Close();
        _com = null;
        _data = null;
        _parts = null;
    }

}

}

  • How does thread cause redraw? It might be a simple missing `Invoke` or synchronization (e.g. calling event asynchronously and then calling it again too soon before previous one is finished). – Sinatr Jul 05 '16 at 14:34
  • I think that is exactly what it is doing, because when I change the number of lines drawn it affects the amount of time it runs before it throws this exception. – Otis Statham Jul 05 '16 at 14:38
  • Okay I did a bit of cursory research on Invoke (again, very much new and entirely self taught) and it doesn't seem that this will solve the problem, as the data comes out in a continuous stream. It seems more that synchronization would be the answer. What could it do to fix this? I would guess that I need a way to stop my snippet of code from finishing and simply start anew with the next batch of data. – Otis Statham Jul 05 '16 at 14:43
  • Can you show a complete chain from receiving data and until it appears on screen? Currently you only post event handler which does some drawing and uses UI (so it should run in UI thread), but it's not clear who rise this event, how and when. – Sinatr Jul 05 '16 at 14:53
  • Alrighty, I'll try to give this a go. – Otis Statham Jul 05 '16 at 14:55
  • - Button on the application is pressed at the first snippet runs. - The new RazorIMU creates a thread which with StartCollection creates a continuously updating float array depending on the argument. 'e' is for Euler angles. Every set also includes the time between reads but it goes unused. - This is where I'm a bit iffy since I'm still not entirely sure what I'm doing with delegates and events, but every time the thread fully updates with a new set of data, it is used in the graphics snippet to render to an image box on screen. – Otis Statham Jul 05 '16 at 15:02
  • - Repeat. However, I think it's repeating mid-graphics portion when the thread reads in data too fast, causing the problem. (Also sorry on the poor formatting of the list, I at least tried.) – Otis Statham Jul 05 '16 at 15:02

1 Answers1

0

Problems:

  1. You have to dispose pens.
  2. You are rising event in some created by you thread, so all event handlers must use Invoke before accessing UI elements.

You can use this simple template for event handlers inside Form (apply it to Fusion_CapturedData()) :

public void SomeEventHandler(someparameters)
{
    if (InvokeRequired)
        Invoke((Action)(() => SomeEventHandler(someparameters))); // invoke itself
    else
    {
        ... // put code which should run in UI thread here
    }
}

You are rising event synchronously, it's not possible to get one before another is finished (no need for synchronization as far as I can see it).

Community
  • 1
  • 1
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • Thanks mate! What does that invocation actually do to fix the problem? I'd like to know so I know to use it next time around. – Otis Statham Jul 05 '16 at 17:23