从队列中读取和写入

Reading and writing from queue

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

当写入线程正在工作(写入队列)时,图片框正在显示队列中的图像,但它似乎跳过了很多图像(就像它正在读取刚刚添加到 "list" 通过写入线程而不是 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

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

首先,开启Option Strict。其次,您不应该从另一个线程访问 UI 控件。核心问题是您并没有真正将 300 多张 不同的图像 放入队列中。相反,代码将下一张图像一遍又一遍地重新绘制到 same 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 方法。短暂的延迟,我可以获得 "Object is in use elsewhere" 异常。

对调试报告的更改使正在发生的事情更加清楚:

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

tag是入队时分配给它的编号,rCount是"Dequeue count"还是在队列中的位置:

tag 13 as # 04
tag 16 as # 05
tag 20 as # 06
tag 24 as # 07
tag 28 as # 08

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


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

' 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 对象发生变化,因此它似乎没有泄漏。