如何将位图图像绘制为从背景线程形成



我有一个套接字。BeginReceive 异步套接字连接,它启动自己的线程。 我正在接收大量不适合该方法第一遍的数据,并且由于数据是许多重复出现的 xml 标记,因此无法知道何时完成接收。

接收到数据时,它被解析并使用 graphics.fromImage(bitmap) 绘制到内存中的位图图像上。 为了绘制这个,图形。DrawImage(位图)在我的重写绘制事件中,以允许在调整大小,最小化等时重新绘制表单。

我使用 Invoke 调用来发出信号以使表单无效以启动绘制事件,这可能不是正确的方法,这就是我问你们的原因。

我遇到的问题是图形对象在从 BeginReceive 方法创建的线程以及主 UI 线程上被调用,同时引发异常。

问题 1:有没有一种好方法可以知道我何时收到套接字上的所有数据? 请记住,它不会在第一次传递时接收所有数据。 如果是这样,我可以以某种方式发出绘画事件的信号。

问题 2:我是否应该在某处阻止非 UI 线程以避免跨线程错误,然后调用主线程来绘制表单? 如果是这样,什么是好的流程?

这是我大部分时间都在做的事情:

private void paint_the_image()
{
Invoke(new Action(() =>
{
this.Invalidate();
}));
}
// the bitmap image is created when the program loads
public void paint_bitmap(int data_type, string text, int x, int y)
{
if (data_type == 1)
{
using( Graphics gr2 = Graphics.FromImage(bitmap_image))
{
gr2.FillRectangle(
Brushes.White, 
35 + (x * 14), 200 + (y * 18), 14, 18);
gr2.DrawString(
text, new Font("Lucida Console", 14), 
newBrush, 35 + (x * 14), 200 + (y * 18));
}
}
}
//paint event
private void main_sockets_Paint(object sender, PaintEventArgs e)
{
using(Graphics gr2 = Graphics.FromImage(bitmap_image))
{
gr2.DrawImage(bitmap_image, 35, 200, 1500, 700);
}
}
// this is called right after the socket connection is made.
public void wait_for_data()
{
if (receive_callBack == null)
{
receive_callBack = new AsyncCallback(on_data_received);
}
async_result = client_socket_connect.BeginReceive(
socket_data_buffer, 0, socket_data_buffer.Length, 
SocketFlags.None, receive_callBack, null);        
}
public void on_data_received(IAsyncResult async)
{
int char_count = 0;
char_count = client_socket_connect.EndReceive(async);
char[] chars = new char[char_count + 1];
System.Text.Decoder decode = System.Text.Encoding.UTF8.GetDecoder();
int char_length = decode.GetChars(socket_data_buffer, 0, char_count, chars, 0);
//processing data....
paint_bitmap(1, txt.ToString(), ex, why);
wait_for_data();
}

我是异步连接的新手,所以请随意将其分开。 任何建议都会有所帮助。

这么多错误...从哪里开始?

好吧,让我们从您的问题开始,重点关注您看到需要关注的线程安全问题:

我使用 Invoke 调用来发出信号以使表单无效以启动 paint 事件,这可能不是正确的方法

实际上,这是在 WinForms 中处理屏幕更新的预期方法:当某些数据更改需要反映在屏幕上时,您将使显示该数据的控件无效,并让控件在引发Paint事件的情况下执行绘图。在许多情况下,控件在内部处理这一切;例如TextBoxLabelListBox等,代码通常会更新控件在内部存储的数据,并且控件处理屏幕刷新。

在您的情况下,您将位图直接绘制到屏幕上(嗯,有点......代码看起来不起作用,但我可以推断出您要做什么)。因此,正确的方法是更新位图,然后使显示位图的控件失效,以便该控件的Paint处理可以将位图绘制到屏幕上。

有没有好方法可以知道我何时收到套接字上的所有数据?

这取决于您所说的"所有数据"是什么意思。您尚未解释应用程序的网络协议,因此我们不知道这意味着什么。在某些情况下,单个 TCP 连接用于传输多个数据块;在这种情况下,应用程序协议必须定义某种方法来分隔这些块。对于二进制协议,这通常是实际数据之前的字节计数。对于基于文本的协议,这可以是特殊字符(例如'\0',换行符,LF/CR对等),甚至可以是某种结构化数据(例如.XML元素)。

在其他情况下,"所有数据"是指在单个连接中发送的每个最后一个字节。在这种情况下,您只需等待流结束指示,即接收操作以 0 字节返回值完成。

我遇到的问题是图形对象在从 BeginReceive 方法创建的线程以及主 UI 线程上被调用,同时引发异常。

据我所知,以上是您的主要问题。解决此问题的最直接方法是同步两个线程。这确实意味着在执行网络 I/O 代码时,UI 线程可能会被暂时阻止,但网络 I/O 代码应该能够足够快地运行,并且运行时间足够短,这不会成为问题。

同步可能如下所示:

private readonly object _lock = new object();
// the bitmap image is created when the program loads
public void paint_bitmap(int data_type, string text, int x, int y)
{
if (data_type == 1)
{
lock (_lock)
{
using( Graphics gr2 = Graphics.FromImage(bitmap_image))
{
gr2.FillRectangle(
Brushes.White, 
35 + (x * 14), 200 + (y * 18), 14, 18);
gr2.DrawString(
text, new Font("Lucida Console", 14), 
newBrush, 35 + (x * 14), 200 + (y * 18));
}
}
}
}
//paint event
private void main_sockets_Paint(object sender, PaintEventArgs e)
{
lock (_lock)
{
e.Graphics.DrawImage(bitmap_image, 35, 200, 1500, 700);
}
}

(我不知道我对Paint事件处理程序所做的更改是否完全正确,但它肯定比您以前的代码更正确,在以前的代码中,您只是出于某种原因将位图的内容绘制到自身)。

上面使用_lock对象来同步对bitmap_image对象的访问,确保一次只有一个线程实际使用它。


<旁白>关于您提供的代码示例的一些要点:示例中没有任何地方实际调用paint_the_image(),也没有显示txt变量的声明或初始化。exwhy变量(顺便说一下,命名非常糟糕)也没有显示,但这似乎不是主要问题的核心。

底线:该示例远非好的、最小的完整的代码示例,这些示例将成为每个好的 Stack Overflow 问题的一部分,但鉴于这些关键细节的遗漏,它尤其糟糕。如果你觉得你没有得到你需要的帮助,你应该花时间清理你的问题,以便它 a) 更集中(即一次处理一个问题,并确保你在继续下一个问题之前让该部分干净利落地工作),以及 b) 包括所需的 MCVE。


现在,关于此代码的其他一些想法:

  1. 上述同步的替代方法是对渲染进行双缓冲(甚至三重缓冲)。无论如何,您似乎每次都会清除位图(顺便说一下,您可以使用Graphics.Clear()方法),因此您无需将图像从一帧保留到下一帧。

因此,您可以只维护两个Bitmap对象并交替绘制它们。您仍然需要同步,但这可以通过以下形式完成:在绘制时锁定 UI 线程,并仅在交换缓冲区时锁定网络 I/O 代码。由于交换缓冲区相当于简单地交换数组中的引用甚至标志或索引(取决于您如何实现缓冲区"链"),因此可以保证快速,并且永远不会延迟 UI 线程任何长时间。

例如:

private readonly object _lock = new object();
private bool _useBufferB;
// the bitmap image is created when the program loads
public void paint_bitmap(int data_type, string text, int x, int y)
{
if (data_type == 1)
{
// Note that this test is reversed from the Paint handler one
using( Graphics gr2 =
Graphics.FromImage(_useBufferB ? bitmap_image : bitmap_imageB))
{
gr2.FillRectangle(
Brushes.White, 
35 + (x * 14), 200 + (y * 18), 14, 18);
gr2.DrawString(
text, new Font("Lucida Console", 14), 
newBrush, 35 + (x * 14), 200 + (y * 18));
}
lock (_lock)
{
_useBufferB = !_useBufferB;
}
}
}
//paint event
private void main_sockets_Paint(object sender, PaintEventArgs e)
{
lock (_lock)
{
e.Graphics.DrawImage(
_useBufferB ? bitmap_imageB : bitmap_image, 35, 200, 1500, 700);
}
}
    接收
  1. 数据时遇到多个问题。

由于以下几个原因,以下代码存在问题:

int char_count = 0;
char_count = client_socket_connect.EndReceive(async);
char[] chars = new char[char_count + 1];

首先,如果您只是要在下一条语句中为char_count赋值,则没有理由将初始化为0。但更成问题的是,除非您使用单字节字符编码(例如 ASCII),否则没有理由期望EndReceive()返回的字节计数对应于字符计数

这已经够糟糕的了,但是你有这个:

System.Text.Decoder decode = System.Text.Encoding.UTF8.GetDecoder();
int char_length = decode.GetChars(socket_data_buffer, 0, char_count, chars, 0);

使用Decoder对象可能是一件好事。 除了你做错了。使用Decoder而不仅仅是调用Encoding.GetString()或类似内容的全部意义在于,Decoder对象有一个内部缓冲区来存储不完整的字符数据,以便在后续接收时它可以从中断的地方继续,并确保正确处理在接收操作中拆分的多字节字符。当您丢弃Decoder对象并为每个接收操作创建一个全新的对象时,您也会丢弃此内部缓冲区并否定使用Decoder的好处。

别这样。

更好的可能是这样的:

private readonly Decoder _decoder = new Encoding.UTF8.GetDecoder();
public void on_data_received(IAsyncResult async)
{
int byte_count = client_socket_connect.EndReceive(async);
char[] chars = new char[_decoder.GetCharCount(socket_data_buffer, 0, byte_count)];
_decoder.GetChars(socket_data_buffer, 0, byte_count, chars, 0);
//processing data....
paint_bitmap(1, new string(chars), ex, why);
wait_for_data();
}

虽然是肯定的,但如果没有一个好的代码示例,就不可能确定这是完全正确的事情(即,你真的想把解码的文本直接传递给paint_bitmap()方法,还是你打算先进一步处理该文本?)。

  1. 当然,在某些时候,您肯定会希望引入对paint_the_image()方法的调用。毕竟,没有它,UI 线程将不知道重绘任何东西。

  2. 也许这个评论应该是第一个......我不知道。我只是想把以上所有内容都排除在外,因为如果你想以后使用这些技术,无论如何你都需要知道这些东西。

但是,真的:我想知道你为什么要使用Bitmap来完成所有这些。您似乎所做的只是将文本数据复制到位图,Winforms确实具有各种其他文本友好控件,您可以使用它们来执行此操作。即使是一个简单的TextBox(将Multiline设置为true以记录多行,如果您希望使用无法修改contets,请将ReadOnly设置为true),或ListBox,或RichTextBox,或Label,或...

有很多选择,所有这些选择都比处理自己的位图缓冲区要渲染更容易,可能更有效。

相关内容

  • 没有找到相关文章

最新更新