1

I'm saving a set of ~300 bitmaps in a concurrent queue. I'm doing this for an over-tcp video streaming program. If the server slows down I save the received bitmaps in this queue (buffering). I created a separate project to test this but I'm having some problems.

While the writing thread is working (writing to the queue) the picture box is showing the images from the queue but it seems that it skips many of them(it is like it's reading the picture just added in the "list" by the writing thread-not FIFO behaviour). When the writing thread finishes the picture box it blocks although the loop in which I read from the queue is still working (when the picture box blocks the queue is not empty).

Here's the code:

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

There's no error. I think there might be copies in the queue(because the picture box appears to freeze)? I tried the same code with integers and it works perfectly. What's the problem?

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
sergiu reznicencu
  • 1,039
  • 1
  • 11
  • 31
  • It is possible that you are getting an unreported exception. Since it looks like only 3 of 4 objects are being disposed, maybe the dreaded Generic GDI error or some other is being thrown when you run out of objects – Ňɏssa Pøngjǣrdenlarp Aug 02 '16 at 18:23
  • What do you mean? I only dispose the picture boc image and one bitmap("bit"). Do you mean those? – sergiu reznicencu Aug 02 '16 at 18:29

1 Answers1

1

First, turn on Option Strict. Second, you should not access UI controls from another thread. The core problem is that you aren't really putting 300+ different images in the que. Rather, the code redraws the next image to the same Bitmap object over and over. You are also using a potentially stale graphics object.

Some other things might be artifacts of trying to get it to work, but there is no reason to clone the image for display - it just results in one more thing to dispose of.

This is using the same crop_bit image over and over.

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)   

Using the same crop_bit means that by time the Read method processes que(4) it might have been changed to image 5; then 6; then 7 by the Write method. With a short delay, I could get "Object is in use elsewhere" exceptions.

A change to the debug reporting makes it a bit clearer what is going on:

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

tag is the number assigned to it when it went into the queue, rCount is it's "Dequeue count" or what position it was in the queue:

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

The second number is correct, but you can see that the 14th and 15th image objects were overwritten by image 16. When the writer finishes, you are left with many copies of the last image loaded.


Fixed with the tag used to mark the item index and reporting done in the Reader method - when they come 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

I ran some 2000+ thru as a test and didn't see GDI Object change at all, so it doesn't seem to leak.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • But what is wrong about using the same crop_bit again and again? (using your approach it works although the ram goes crazy). I mean the method Enqueue uses a copy of crop_bit. It is passed by val not by reference. It should work with the code I wrote. – sergiu reznicencu Aug 03 '16 at 07:04