基于线程的绘图中的 InvalidOperationException

InvalidOperationException in thread based drawing

我是编码游戏的新手,所以我不太确定如何解决这个问题。我的代码从一个线程中提取信息,该线程从一个 9DOF 传感器中获取数据,该传感器以三个欧拉角的形式出现。然后使用数据生成点,在屏幕上用绘图 class 从这些点创建一个圆圈。 To 完美地工作了一段时间,但最终总是会给出这个异常,说在 System.Drawing.dll 中发生了一个类型为 'System.InvalidOperationException' 的未处理的异常,并附加信息说该对象当前正在别处使用。在仔细研究并尝试绘制的内容后,我得出(可能不正确的)结论,即线程发送数据的速度快于主代码渲染绘图的速度。我怎样才能防止这种情况发生?下面是代码的摘录。

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;
        }

这是主要代码。线程只是以接收信息的速度发送信息,所以我认为我不需要把它放在这里,除非有人另有说明。

{ public 委托 void RazorDataCaptured(float[] 数据, 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;
    }

}

}

问题:

  1. 你必须dispose pens
  2. 您正在创建一些由您创建的线程中的上升事件,因此所有事件处理程序在访问 UI 元素之前必须使用 Invoke

您可以在 Form 中使用这个简单的事件处理程序模板(将其应用于 Fusion_CapturedData()):

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

你正在同步上升事件,不可能在另一个完成之前得到一个(据我所知不需要同步)。