1

我在并发队列中保存了一组约 300 个位图。我正在为 over-tcp 视频流程序执行此操作。如果服务器变慢,我将接收到的位图保存在此队列中(缓冲)。我创建了一个单独的项目来测试它,但我遇到了一些问题。

当写入线程正在工作(写入队列)时,图片框正在显示队列中的图像,但它似乎跳过了许多图像(就像它正在读取写入线程刚刚添加到“列表”中的图片-不是 FIFO 行为)。当写入线程完成图片框时,它会阻塞,尽管我从队列中读取的循环仍在工作(当图片框阻塞时队列不为空)。

这是代码:

Imports System
Imports System.Drawing
Imports System.IO
Imports System.Threading
Imports System.Collections.Concurrent

Public Class Form1
    Dim writeth As New Thread(AddressOf write), readth As New Thread(AddressOf read)
    Dim que As New ConcurrentQueue(Of Bitmap), finished As Boolean


    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        'Start button

        writeth.Start()
        readth.Start()    
    End Sub

    Sub draw(ByRef pic As Bitmap)
        If PictureBox1.Image IsNot Nothing Then
            PictureBox1.Image.Dispose()
            PictureBox1.Image = Nothing
        End If

        PictureBox1.Image = pic
    End Sub

    Sub read()
        Dim bit As Bitmap
        While (Not finished Or Not que.IsEmpty)
            If que.TryDequeue(bit) Then
                draw(bit.Clone)

                'Still working after the writing stopped
                If finished Then Debug.Print("picture:" & que.Count)

                Thread.Sleep(2000) 'Simulates the slow-down of the server
            End If
        End While
    End Sub

    Sub write()
        Dim count As Integer = 0
        Dim crop_bit As New Bitmap(320, 240), bit As Bitmap
        Dim g As Graphics = Graphics.FromImage(crop_bit)

        For Each fil As String In Directory.GetFiles(Application.StartupPath & "/pictures")
            count += 1
            Debug.Print(count)

            bit = Image.FromFile(fil)
            g.DrawImage(bit, 0, 0, 320, 240)

            que.Enqueue(crop_bit)
            bit.Dispose()
        Next
        finished = True
        'At this point the picture box freezes but the reading loop still works
    End Sub
End Class

没有错误。我认为队列中可能有副本(因为图片框似乎冻结了)?我用整数尝试了相同的代码,它工作得很好。有什么问题?

4

1 回答 1

1

首先,开启Option Strict. 其次,您不应该从另一个线程访问 UI 控件。核心问题是您并没有真正在队列中放置 300 多个不同的图像。相反,代码一遍又一遍地将下一个图像重绘到同一个Bitmap 对象。您还使用了可能过时的图形对象。

其他一些事情可能是试图让它工作的产物,但没有理由克隆图像以进行显示 - 它只会导致另外一件事情要处理。

crop_bit这是一遍又一遍地 使用相同的图像。

Sub write()
    Dim count As Integer = 0
    Dim crop_bit As New Bitmap(320, 240), bit As Bitmap
    Dim g As Graphics = Graphics.FromImage(crop_bit)
    ...
    que.Enqueue(crop_bit)   

使用相同crop_bit的方法意味着,随着时间的推移,Read方法处理que(4)它可能已更改为图像 5;然后是 6;然后7按Write方法。稍作延迟,我就会得到“对象正在其他地方使用”异常。

对调试报告的更改使它更清楚发生了什么:

' in "read"
Console.WriteLine("tag {0:00} as # {1:00}", 
        bit.Tag.ToString, rCount)

tag是它进入队列时分配给它的数字,rCount它是“出队计数”还是它在队列中的位置:

标签 13 作为 #04
标签 16 作为 #05
标签 20 作为 #06
标签 24 作为 #07
标签 28 作为 #08

第二个数字是正确的,但是您可以看到第 14 和第 15 个图像对象被图像 16 覆盖。当编写器完成时,您会留下许多已加载的最后一个图像的副本。


修复了用于在方法中标记项目索引和报告的标签Reader- 当它们出现

' for picture box display
Private DisplayImg As Action(Of Bitmap)
...
' initialize when you start the work:
DisplayImg = AddressOf Display

Sub Reader()
    Dim bit As Bitmap = Nothing
    Do
        If que.TryDequeue(bit) Then
            ' do not acccess the UI from a different thread
            ' we know we are on a diff thread, just Invoke
            pbImg.Invoke(DisplayImg, bit)

            ' report on the item
            Console.WriteLine(bit.Tag.ToString)
            Thread.Sleep(100) 'Simulates the slow-down of the server
        End If
    Loop Until (finished AndAlso que.IsEmpty)
End Sub

Sub Writer()
    Dim count As Integer = 0
    Dim crop_bit As Bitmap

    ' enumerate files is more efficient - loads one at a time
    For Each fil As String In Directory.EnumerateFiles(filepath, "*.jpg")
        count += 1
        ' need a NEW bitmap for each file
        crop_bit = New Bitmap(320, 240)

        ' need to use and dispose of NEW graphics for each
        '  use a NEW img from file and dispose of it
        Using g As Graphics = Graphics.FromImage(crop_bit),
             img = Image.FromFile(fil)
            g.DrawImage(img, 0, 0, 320, 240)
        End Using
        ' put a collar on them
        crop_bit.Tag = count.ToString
        que.Enqueue(crop_bit)
    Next
    finished = True
End Sub

Sub Display(pic As Bitmap)
   '... the same,
   ' handles the display AND disposal
   ...
End Sub

作为测试,我运行了大约 2000 多次,根本没有看到 GDI 对象发生变化,所以它似乎没有泄漏。

于 2016-08-02T21:39:46.820 回答