【问题标题】:Threading and graphics plotting in C#C# 中的线程和图形绘图
【发布时间】:2018-06-07 02:01:38
【问题描述】:

我正在将 C# 上的 GUI 开发为 Windows 窗体应用程序。我正在使用 TCP/IP 协议从 GUI 服务器(作为 Windows 窗体应用程序)上的客户端(作为控制台应用程序)接收数据流。流模式接收如下:

我在绘图时遇到的麻烦。我在图片框中的图像上绘制两个图形对象。在绘制图形时,第二个显示在绘制时闪烁。 我真的不明白为什么它会在第二个(红色)图形中产生闪烁。 我在这里分享我的服务器代码,请告诉我我错在哪里。

Server Code (GUI): 
    using System;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Net;
    using System.Net.Sockets;
    using System.Collections;
    using System.Threading; 

        namespace GUI_Server
         {
          public partial class Form1 : Form
          {
            public Form1()
            {
              InitializeComponent();           
            }

          Thread listener; 
          Int32 port = 3000;
          IPAddress ip = IPAddress.Parse("127.0.0.1");
          ArrayList nSockets;
          String[] PART = null;
          String data = null;

          private System.Drawing.Graphics g, path;

          Double w, x, y, z;

          private void Form1_Load(object sender, EventArgs e)
           {
              pictureBox1.Image = new Bitmap("Image");
              g = pictureBox1.CreateGraphics();
              path = Graphics.FromImage(pictureBox1.Image);

              nSockets = new ArrayList();
              label1.Text = "IP Address:" + ip;
              listener = new Thread(listen);
              listener.Start();
           }

          public void listen()//thread
           {
               TcpListener tcpListener = new TcpListener(ip, port);
               tcpListener.Start();

               while (true)
               {
                   Socket handlerSocket = tcpListener.AcceptSocket();
                   if (handlerSocket.Connected)
                {
                    Control.CheckForIllegalCrossThreadCalls = false;
                    label2.Text = "Connected";
                    lock (this)
                    {
                        nSockets.Add(handlerSocket);
                    }
                    ThreadStart thdstHandler1 = new ThreadStart(handlerThread1);
                    Thread thdHandler1 = new Thread(thdstHandler1);
                    thdHandler1.Start();
                   }
               }
           }

           public void handlerThread1() // thread
           {
            Socket handlerSocket = (Socket)nSockets[nSockets.Count - 1];
            NetworkStream networkStream = new NetworkStream(handlerSocket);
            Byte[] bytes = new Byte[1024];

            int k;

            lock (this)
            {
                while ((k = networkStream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    data = Encoding.ASCII.GetString(bytes, 0, k);
                    PART = data.Split('\t');

                    if (Convert.ToDouble(part[0]) == 1)
                    {
                        w = (Convert.ToDouble(part[1]) / 0.0347) + 60;
                        x = (Convert.ToDouble(part[2]) / 0.0335) + 656;
                        textBox1.Text = ("Tag ID_Blue: " + part[0] + "\t" + "X: " + part[2] + "\t" + "Y: " + part[1]);                       
                    }
                    g.DrawRectangle(new Pen(Color.Blue, 3), Convert.ToInt32(x), 
                    Convert.ToInt32(w), 6, 6); // first graphic

                    if (Convert.ToDouble(part[0]) == 2)
                    {
                        y = (Convert.ToDouble(part[1]) / 0.0347) + 60;
                        z = (Convert.ToDouble(part[2]) / 0.0335) + 656;
                        textBox2.Text = ("Tag ID_Red: " + part[0] + "\t" + "X: " + part[2] + "\t" + "Y: " + part[1]);                       
                    }
                    g.DrawRectangle(new Pen(Color.Red, 3), Convert.ToInt32(z), Convert.ToInt32(y), 6, 6); // second graphic

                     Thread.Sleep (50);
                     pictureBox1.Refresh();


                    byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
                    networkStream.Write(msg, 0, msg.Length);
                }               
            }
            handlerSocket = null;
        }

有没有办法只刷新图形,而不是整个图片框。是否因为刷新图片框而出现闪烁?

【问题讨论】:

  • 那么你应该删除这一行Control.CheckForIllegalCrossThreadCalls = false;(参见here,而是从你的后台线程调用InvokeBeginInvoke来更新用户界面。不确定这是否是你的一部分问题,但肯定是候选人。另外,我可能不明白你在做什么,但看起来你有可能启动很多线程。这似乎是一个经典的生产者/消费者,考虑@987654329 @
  • 旁注 - 不建议将 ArrayList 用于新代码,请改用 List<T> 并且您应该避免调用 lock(this) 请参阅 this SO answer 以很好地解释您为什么应该这样做't。

标签: c# multithreading plot picturebox


【解决方案1】:

让我们尝试清理一下,看看我们是否无法解决闪烁问题。我不确定它是否与将 Control.CheckForIllegalCrossThreadCalls 设置为 false 相关,但还有其他事情需要清理,我们也会删除该调用,希望它能让你的事情运行得更好。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();           
    }

    const int port = 3000;
    ManualResetEvent _closing = new ManualResetEvent(false);

    private void Form1_Load(object sender, EventArgs e)
    {
        pictureBox1.Image = new Bitmap("Image");
        var ip = IPAddress.Parse("127.0.0.1");
        label1.Text = "IP Address:" + ip;
        Task.Run(() => Listen());
    }

    private void Listen()
    {
        var tcpListener = new TcpListener(ip, port);
        tcpListener.Start();

        while (!_closing.WaitOne(100))
        {
            if (tcpListener.Pending())
            {
                tcpListener.AcceptSocketAsync().ContinueWith( (socket) =>
                {
                    NetworkStream ns = null;
                    MemoryStream ms = null;
                    try
                    {
                        if (!socket.Connected) return;
                        label2.Invoke(() => label2.Text = "Connected");

                        ns = new NetworkStream(socket);
                        ms = new MemoryStream();
                        ns.CopyTo(ms); // Copies from the NetworkStream to the MemoryStream. Should use a default buffer size of 81920
                        var msg = ms.ToArray();
                        var parts = Encoding.ASCII.GetString(msg).Split('\t');
                        if (parts.Length != 3) return; // simple error check

                        // You could use TryParse for error checking                     
                        var w = (double.Parse(part[1]) / 0.0347) + 60.0; 
                        var x = (double.Parse(part[2]) / 0.0335) + 656.0;                              

                        string tag = "";
                        Color color;
                        if (part[0].Equals("1", StringComparison.Ordinal))
                        {
                            tag = $"Tag ID_Blue: {part[0]}\tX {part[2]}\tY: {part[1]};
                            color = Color.Blue;
                        }
                        else if (part[0].Equals("2", StringComparison.Ordinal))
                        {
                            tag = $"Tag ID_Red: {part[0]}\tX {part[2]}\tY: {part[1]};
                            color = Color.Red;
                        }
                        else return; // Shouldn't hit this

                        UpdateUiDrawRectangle(tag, color, (int)x, (int)w);

                        // Your code was writing the same message back
                        ns.Write(msg, 0, msg.Length);
                    }
                    finally
                    {
                        // Need to cleanup resources
                        ns?.Dispose();
                        ms?.Dispose();
                        socket.Close();
                    }
                }
            }
        }
    }

    private void UpdateUiDrawRectangle(string tag, Color color, int x, int w)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(() => UpdateUiDrawRectange(tag, color, x, w));
            return;
        }
        Graphics g = null;
        Pen pen = null;
        try
        {
            pen = new Pen(color, 3);
            g = pictureBox1.CreateGraphics();
            g.DrawRectangle(pen, x, w, 6, 6);
            textBox2.Text = tag;
        }
        finally
        {
            g?.Dispose();
            pen?.Dispose();
        }
        pictureBox1.Invalidate(); // Instead of Refresh
    }

代码和更改说明。
首先,我们使用等待句柄(在本例中为ManualResetEvent)而不是while(true) 循环。这将允许您将代码添加到您的FormClosingFormClosed 事件中以设置将导致您的循环退出的事件。 WaitOne 调用将检查句柄是否已发出信号。如果没有,它将阻塞 100 毫秒(根据需要进行调整),如果仍然没有发出信号,将执行循环中的代码。接下来我们检查是否有挂起的连接,然后我们使用异步接受方法返回一个带有套接字的Task 对象,一旦建立连接,它将返回。一旦套接字被接受,我们就可以使用ContinueWith 模式来使用它。在这段代码中,我们处理打开流并读取消息,然后准备绘制矩形。我添加了一个新方法来绘制矩形。请注意,在该方法中,它会检查 InvokeRequired,如果需要,将使用 Invoke 调用该方法,以便我们可以在 UI 线程上执行所有 UI 操作。我还添加了Dispose 资源调用。您会注意到我们不再需要ArrayList 或套接字集合,ContinueWith 负责使用我们打开的套接字。我们也不再需要锁定部分代码,并且我们已经能够删除大部分全局/类范围的变量并改用局部变量。我们会在需要时创建 Graphics 对象,然后将其丢弃。

可以进行更多清理,但这应该会让您朝着正确的方向前进。请注意,可能有错字,因为我是手写的,没有尝试编译它,但类似的东西应该很小且易于清理。

【讨论】:

  • 非常感谢...感谢您的回复和如此巨大的努力。我将这样实现我的代码...但是您能告诉我避免图像上绘制的图形闪烁的可能方法吗图片框?
  • @SN25 - 我认为这段代码将有助于消除闪烁。我还没有测试过,所以我不能肯定地说,但试试这个看看。至少应该会好一点。
  • 我在程序中面临的当前问题是绘图延迟...是由于 TCP/IP 通信吗?服务器端处理速度慢吗?
  • @SN25 - plotting delay 是什么意思。你的意思是画的不够快吗?很难说是什么原因。可能是通讯。您必须添加日志记录/跟踪或其他任何内容,并缩小花费时间的范围。如果您已经实现了上述代码,则在检查通信之间仅暂停 100 毫秒,您可以将其调整为 50 或类似的值,看看是否有区别。首先,我可能会添加一些跟踪,看看您是否真的获得了这么快的连接。
  • 标签(图形)移动与实时移动不同步。就像我拿着标签走了一会儿,然后突然停下来,所以标签在延迟几秒钟后停止在显示屏上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-30
  • 1970-01-01
相关资源
最近更新 更多