-
Mar 4th, 2019, 10:16 AM
#1
Thread Starter
Member
[RESOLVED] Picturebox memory leak with .refresh() to redraw it.
I am writing to two pictureboxes via a thread constantly reading images as Bitmaps (video frames). This is all working fine except for the form updating.
However the pictureboxes are not updating at the same speed. Picturebox 2 is almost realtime whilst picturebox 1 is updating 1 frame in about every 10.
Picturebox 2 is a modified version of the original. Everything is diposed of and there are no memory leaks until i try to get the form to update.
The code below is safe and doesn't increase memory usage.
VB.NET Code:
' cross thread to update the picturebox If Me.InvokeRequired Then BeginInvoke(Sub() If pic1.Image IsNot Nothing Then pic1.Image.Dispose() pic1.Image = Nothing If MS IsNot Nothing Then MS.Dispose() GC.Collect() pic1.Image = Bmp Else pic1.Image = Bmp End If If pic2.Image IsNot Nothing Then pic2.Image.Dispose() pic2.Image = Nothing If MS IsNot Nothing Then MS.Dispose() GC.Collect() pic2.Image = scanimage(Bmp) Else pic2.Image = scanimage(Bmp) End If End Sub) Else ' nothing here yet End If
However, when I try to refresh either a picturebox or the form with
pic1.refresh() or me.refresh() after pic1.Image = Bmp
Memory increases rapidly by 10MB + each image until it hits about 2GB and crashes.
VB.NET Code:
' cross thread to update the picturebox If Me.InvokeRequired Then BeginInvoke(Sub() If pic1.Image IsNot Nothing Then pic1.Image.Dispose() pic1.Image = Nothing If MS IsNot Nothing Then MS.Dispose() GC.Collect() pic1.Image = Bmp Else pic1.Image = Bmp pic1.refresh() '<------------ refresh causes rapid memory leak End If If pic2.Image IsNot Nothing Then pic2.Image.Dispose() pic2.Image = Nothing If MS IsNot Nothing Then MS.Dispose() GC.Collect() pic2.Image = scanimage(Bmp) Else pic2.Image = scanimage(Bmp) End If End Sub) Else ' nothing here yet End If
NB, The Garbage collector statement doesn't need to be there - its not doing anything.
Does anyone have any idea how to get the pictureboxes to redraw without memory leaks ?
Last edited by mickle026; Mar 4th, 2019 at 10:48 AM.
-
Mar 4th, 2019, 11:00 AM
#2
Re: Picturebox memory leak with .refresh() to redraw it.
Except for not knowing what MS is you might be over thinking this.
Try this. Does it work?
Code:
If Me.InvokeRequired Then
BeginInvoke(Sub()
If pic1.Image IsNot Nothing Then
pic1.Image.Dispose()
If MS IsNot Nothing Then MS.Dispose()
pic1.Image = Bmp
Else
pic1.Image = Bmp
End If
If pic2.Image IsNot Nothing Then
pic2.Image.Dispose()
If MS IsNot Nothing Then MS.Dispose()
pic2.Image = scanimage(Bmp)
Else
pic2.Image = scanimage(Bmp)
End If
Me.Refresh()
End Sub)
Else
' nothing here yet
End If
-
Mar 4th, 2019, 11:21 AM
#3
Re: Picturebox memory leak with .refresh() to redraw it.
What is scanimage? What does it do?
You might want to use Invalidate rather than Refresh. Refresh forces the code to stop and draw right then, whereas Invalidate just notes that the PB is dirty, so it gets drawn as soon as the UI thread has some time. This is generally nicer, though it can also mean that drawing doesn't happen, if the UI thread is NEVER free. That doesn't seem likely to be an issue, in your case.
My usual boring signature: Nothing
-
Mar 4th, 2019, 11:35 AM
#4
Thread Starter
Member
Re: Picturebox memory leak with .refresh() to redraw it.
Hi, thanks
Scanimage is another routine that alters the bitmap in a lockbits/marshalling function. There's no memory leak there.
Invalidate updates just a few more frames and is very similar to not using it.
Something interesting in what your response is though - the UI thread, i need to look at that
I have just tried a timer refresh interval of 20ms
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
pic1.Refresh()
pic2.Refresh()
End Sub
This updates it without memory increase - so the I think the UI thread is where i need to be , but its still slow on picturebox 1.
-
Mar 4th, 2019, 01:43 PM
#5
Re: Picturebox memory leak with .refresh() to redraw it.
I ran this test and:
1. No memory leaks
2. No out of mem
3. UI was semi-responsive
One refresh seems to take care of any control that is Invaidated. I changed the BeginInvoke to Invoke.
Code:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Static ptsk As task
If ptsk Is Nothing OrElse ptsk.Status = TaskStatus.RanToCompletion Then
ptsk = Task.Run(Sub() ShowPics())
End If
End Sub
Private Sub ShowPics()
Dim pth As String = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
Dim fs As List(Of String) = IO.Directory.GetFiles(pth).ToList
Dim ie As IEnumerable(Of String)
'47 pics after select
ie = From f In fs
Where IO.Path.GetExtension(f).StartsWith(".jp") OrElse IO.Path.GetExtension(f) = ".png"
Select f
For x As Integer = 1 To 3
For Each f As String In ie
If Me.InvokeRequired Then
Me.Invoke(Sub()
If PictureBox1.Image IsNot Nothing Then
PictureBox1.Image.Dispose()
End If
PictureBox1.Image = Image.FromFile(f)
If PictureBox2.Image IsNot Nothing Then
PictureBox2.Image.Dispose()
End If
PictureBox2.Image = Image.FromFile(f)
PictureBox1.Refresh()
End Sub)
End If
Threading.Thread.Sleep(1) 'larger more responsive UI
Next
Next
End Sub
-
Mar 4th, 2019, 02:25 PM
#6
Thread Starter
Member
Re: Picturebox memory leak with .refresh() to redraw it.
Originally Posted by dbasnett
I ran this test and:
1. No memory leaks
2. No out of mem
3. UI was semi-responsive
One refresh seems to take care of any control that is Invaidated. I changed the BeginInvoke to Invoke.
Code:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Static ptsk As task
If ptsk Is Nothing OrElse ptsk.Status = TaskStatus.RanToCompletion Then
ptsk = Task.Run(Sub() ShowPics())
End If
End Sub
Private Sub ShowPics()
Dim pth As String = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
Dim fs As List(Of String) = IO.Directory.GetFiles(pth).ToList
Dim ie As IEnumerable(Of String)
'47 pics after select
ie = From f In fs
Where IO.Path.GetExtension(f).StartsWith(".jp") OrElse IO.Path.GetExtension(f) = ".png"
Select f
For x As Integer = 1 To 3
For Each f As String In ie
If Me.InvokeRequired Then
Me.Invoke(Sub()
If PictureBox1.Image IsNot Nothing Then
PictureBox1.Image.Dispose()
End If
PictureBox1.Image = Image.FromFile(f)
If PictureBox2.Image IsNot Nothing Then
PictureBox2.Image.Dispose()
End If
PictureBox2.Image = Image.FromFile(f)
PictureBox1.Refresh()
End Sub)
End If
Threading.Thread.Sleep(1) 'larger more responsive UI
Next
Next
End Sub
Hi
Thanks for the reply.
I don't have any memory leaks running the picturebox updates on the same thread. The problem occurs because its a cross thread update. The picturebox refresh called from another thread is causing the memory leak.
What my program does is receive data through a named pipe, rebuild the bitmap as a memory stream (MS) from a byte array, and then to bitmap
Dim img As Byte() = byteList.ToArray() <-- received from pipe from external program such as ffmpeg
Dim MS As New MemoryStream(img) <--- create a memory stream
Dim Bmp As New Bitmap(MS) <--- create a bitmap from memory stream
The problem i am having is showing the images realtime in pictureboxes. If I do just one picturebox, its fine - it works ok with no refreshing. As soon as there is a second picturebox, the second draws ok, but the first is intermittant - even with no bitmap manipulation. Refreshing with an invoke causes a rapid memory leak.
I think with your help the solution is to send the thread to sleep, ie slow it down. It seems that data is arriving faster than it can draw the image.
Threading.Thread.Sleep(5) . 5 milliseconds is not quite enough to allow the pictureboxes to fully redraw without a glitch
Threading.Thread.Sleep(10). However 10 milliseconds is !!! - 15 milliseconds is even better
VB.NET Code:
If Me.InvokeRequired Then BeginInvoke(Sub() If pic1.Image IsNot Nothing Then pic1.Image.Dispose() pic1.Image = Nothing If MS IsNot Nothing Then MS.Dispose() pic1.Invalidate() pic1.Image = Bmp Else pic1.Image = Nothing pic1.Invalidate() pic1.Image = Bmp End If If pic2.Image IsNot Nothing Then pic2.Image.Dispose() pic2.Image = Nothing If MS IsNot Nothing Then MS.Dispose() pic2.Invalidate() pic2.Image = scanimage(Bmp) Else pic2.Image = Nothing pic2.Invalidate() pic2.Image = scanimage(Bmp) End If pic1.Refresh() End Sub) Threading.Thread.Sleep(15) Else ' nothing here yet End If
Just so you are aware, with thread sleep the refresh isn't causing memory leaks - I wonder why it does without sleeping ? ?
With the thread sleep it doesn't actually need the pic1.refresh()
My bitmap generation is in the same thread, so its sequential its not creating another first unless the UI doesn't release it because of the redraw?
Last edited by mickle026; Mar 4th, 2019 at 02:35 PM.
-
Mar 4th, 2019, 02:50 PM
#7
Thread Starter
Member
Re: Picturebox memory leak with .refresh() to redraw it.
Actually leaving out pic1.refresh() make the UI thread more responsive. With the thread sleep it doesn't need it.
Many thanks
-
Mar 4th, 2019, 03:34 PM
#8
Re: Picturebox memory leak with .refresh() to redraw it.
Originally Posted by mickle026
Actually leaving out pic1.refresh() make the UI thread more responsive. With the thread sleep it doesn't need it.
Many thanks
I changed the example to have a sender and receiver to more closely model receipt of data over a Pipe.
Code:
Private rcvd As New Threading.AutoResetEvent(False) 'set upon receipt
Private Q As New Concurrent.ConcurrentQueue(Of Byte()) 'queue of Byte()
Private Sub RcvPics()
Do
rcvd.WaitOne()
Dim b() As Byte
While Q.Count > 0
'get the byte array
If Q.TryDequeue(b) Then
Dim ms As New IO.MemoryStream(b) 'back to MemoryStream
Dim img As Image = Image.FromStream(ms) 'MemoryStream to image
ms.Close()
ms.Dispose()
b = Nothing
'running on thread, no need to check if invoke needed
Me.BeginInvoke(Sub()
If PictureBox1.Image IsNot Nothing Then
PictureBox1.Image.Dispose()
End If
PictureBox1.Image = img
If PictureBox2.Image IsNot Nothing Then
PictureBox2.Image.Dispose()
End If
PictureBox2.Image = img
'PictureBox1.Refresh() 'un-comment as needed
End Sub)
Else
rcvd.WaitOne(50)
End If
End While
Loop
End Sub
Private Sub SendPics()
Dim pth As String = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
Dim fs As List(Of String) = IO.Directory.GetFiles(pth).ToList
Dim ie As IEnumerable(Of String)
ie = From f In fs
Where IO.Path.GetExtension(f).StartsWith(".jp")
Select f
For x As Integer = 1 To 2
For Each f As String In ie
Dim ms As New IO.MemoryStream
Dim img As Image = Image.FromFile(f) 'file to image
img.Save(ms, Drawing.Imaging.ImageFormat.Bmp) 'save in MemoryStream
img.Dispose()
Dim b() As Byte = ms.ToArray() 'get byte array
ms.Close()
ms.Dispose()
Q.Enqueue(b)
rcvd.Set() 'indicate receipt
Next
Next
fs.Clear()
End Sub
Last edited by dbasnett; Mar 4th, 2019 at 04:41 PM.
-
Mar 4th, 2019, 04:11 PM
#9
Re: Picturebox memory leak with .refresh() to redraw it.
Originally Posted by mickle026
Actually leaving out pic1.refresh() make the UI thread more responsive. With the thread sleep it doesn't need it.
Many thanks
That's reasonable. Refresh is a pretty brutal thing to be calling, so you want to do it as infrequently as you can. Adding the image is invalidating the picturebox already, and that's enough to trigger a redraw at the time and choosing of the UI. If that time and choosing is working for you, then that's the best solution. Refresh is for those times when the time and choosing aren't good enough.
My usual boring signature: Nothing
-
Mar 4th, 2019, 04:15 PM
#10
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
What is the secondary thread doing before it calls invoke?
-
Mar 4th, 2019, 04:53 PM
#11
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
Originally Posted by OP
I don't have any memory leaks running the picturebox updates on the same thread. The problem occurs because its a cross thread update. The picturebox refresh called from another thread is causing the memory leak.
You are always updating the picturebox on the same thread. You can't access the picturebox without being on the UI thread. You have fallen into the trap like so many others and not understanding worker threads by thinking to create a secondary thread invoking back to the UI is how multithreading should be done. If all you are doing is creating a new image on this thread to invoke back to the UI will likely cause you more penalties.
-
Mar 4th, 2019, 04:58 PM
#12
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
All objects, regardless of how long they have been in memory, are considered for collection; however, objects that are referenced in managed code are not collected. Use this method to force the system to try to reclaim the maximum amount of available memory.
https://docs.microsoft.com/en-us/dotnet/api/system.gc.collect?view=netframework-4.7.2#System_GC_Collect
-
Mar 4th, 2019, 05:45 PM
#13
Thread Starter
Member
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
Originally Posted by ident
You are always updating the picturebox on the same thread. You can't access the picturebox without being on the UI thread. You have fallen into the trap like so many others and not understanding worker threads by thinking to create a secondary thread invoking back to the UI is how multithreading should be done. If all you are doing is creating a new image on this thread to invoke back to the UI will likely cause you more penalties.
You are quite right that I don't understand it. I am trying to though.. and I understand it a lot more than i did a week ago....
I've got named pipes nailed now thanks to help here
Not being formally educating in programming I've been learning what I need to by trial and error. Always trying to run before I can walk - thats me.... Probably not the best way to do it but I get there in the end. I find this method gets my interest a lot better and I dont get bored with it.
By having these errors its helping me understand why I can't do these things .
Im not just creating a new image on this thread. Im reading it from a stream of bytes from a named pipe from a console application - ie ffmpeg.exe.
Im wanting to display the original image extracted and a modified version (that I edit) as they are read from the pipe.
Currently im just modifying a few colours to see if I can do "the concept" that I thought of - it is working ... perhaps vb.net or my computer is just not fast enough. I eventually want to try and detect objects in the images
I need a little time to ingest the info dbasnett has posted.
The secondary thread is reading the data pipe
Heres the code
VB.NET Code:
Private Sub StartServerPipe() Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg") ServerPipe.WaitForConnection() ServerPipe.WaitForPipeDrain() ' a continual listening loop Dim ImageLength As Integer = 0 ' create a reader Dim byteList As New List(Of Byte) Do Dim B(4095) As Byte Dim L As Integer = ServerPipe.Read(B, 0, 4096) If L > 0 Then ReDim Preserve B(L - 1) byteList.AddRange(B) ' read the pipe until the next bitmap by getting the image length in bytes ' read the three byte header to check if its a BM6 type bitmap If B(1) = &H4D AndAlso B(0) = &H42 Then ImageLength = BitConverter.ToInt32(B, 2) Dim remaining As Integer = ImageLength - byteList.Count Do While remaining > 0 L = ServerPipe.Read(B, 0, Math.Min(B.Length, remaining)) If L > 0 Then ReDim Preserve B(L - 1) byteList.AddRange(B) End If remaining = ImageLength - byteList.Count Loop Dim img As Byte() = byteList.ToArray() Dim MS As New MemoryStream(img) Dim Bmp As New Bitmap(MS) ' cross thread to update the picturebox If Me.InvokeRequired Then BeginInvoke(Sub() If pic1.Image IsNot Nothing Then pic1.Image.Dispose() pic1.Image = Nothing If MS IsNot Nothing Then MS.Dispose() pic1.Invalidate() ' Not updating the input picturebox will increase speed If Me.chkShowImages.Checked = True Then pic1.Image = Bmp End If Else pic1.Image = Nothing pic1.Invalidate() ' Not updating the input picturebox will increase speed If Me.chkShowImages.Checked = True Then pic1.Image = Bmp End If End If If pic2.Image IsNot Nothing Then pic2.Image.Dispose() pic2.Image = Nothing If MS IsNot Nothing Then MS.Dispose() pic2.Invalidate() pic2.Image = scanimage(Bmp) Else pic2.Image = Nothing pic2.Invalidate() pic2.Image = scanimage(Bmp) End If End Sub) Threading.Thread.Sleep(15) Else ' nothing here yet End If ' reset/flush the byte list array byteList.Clear() ' img = Nothing Else 'output is not a valid Bitmap! 'Throw Exception End If End If Loop While ServerPipe.IsConnected ServerPipe.Close() End Sub Private Sub ButAskFFmpegForImage_Click(sender As Object, e As EventArgs) Handles ButAskFFmpegForImage.Click OpenFileDialog1.RestoreDirectory = True OpenFileDialog1.Filter = "Video Files (*.ts, *.mkv, *.mp4, *.avi)|*.ts;*.mkv;*.mp4; *.avi|All Files (*.*)|*.*" OpenFileDialog1.ShowDialog() OpenVideoName = OpenFileDialog1.FileName If OpenVideoName = "" Or OpenVideoName = "(Select a Movie File)" Then ' no file selected Exit Sub End If If File.Exists(Application.StartupPath + "\ffmpeg.exe") Then Dim ffmpegfilepath As String = Application.StartupPath + "\ffmpeg.exe" ' New ProcessStartInfo created Dim p As New Process ' Specify the location of the binary p.StartInfo.FileName = ffmpegfilepath ' single frame 'p.StartInfo.Arguments = " -i """ + OpenVideoName + """ -ss 00:00:1 -s 720x560 -vframes 1 -c:v bmp -y -f image2pipe " + """\\.\pipe\From_FFmpeg""" ' continous stream of frames p.StartInfo.Arguments = " -i """ + OpenVideoName + """ -ss 00:00:5 -s 720x560 -c:v bmp -y -f image2pipe " + """\\.\pipe\From_FFmpeg""" p.StartInfo.UseShellExecute = False ' Use a hidden window (rem these out to debug) p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden p.StartInfo.CreateNoWindow = True p.StartInfo.RedirectStandardError = True p.StartInfo.RedirectStandardOutput = True p.SynchronizingObject = Me p.EnableRaisingEvents = True AddHandler p.OutputDataReceived, AddressOf UpdateDataReceived AddHandler p.ErrorDataReceived, AddressOf UpdateDataReceived p.Start() p.BeginErrorReadLine() p.BeginOutputReadLine() Else MsgBox("No ffmpeg") End If End Sub
I have another thread reading the stderr / stdoutput.
Im personally starting to think that 25fps to 2 pictureboxes is just too much and I should probably only draw at 4 of 5 fps
Last edited by mickle026; Mar 4th, 2019 at 05:50 PM.
-
Mar 4th, 2019, 05:57 PM
#14
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
Not being formally educating in programming I've been learning what I need to by trial and error. Always trying to run before I can walk - thats me.... Probably not the best way to do it but I get there in the end. I find this method gets my interest a lot better and I dont get bored with it.
No shame in that. I picked up my first VB book under the framework in 2002 when I shamefully say I made mistakes at 15 and was in a young institute prison. I had to write everything down on pen and paper and only ever advanced when a member here sitten (I met on another forum) drilled it into me that copying from
other's code was not programming. I went on to get my degree and even though I am a carpenter (well was as quite ill now) I realized trial and error as so many people state is a way to learn is not a great way to learn.
This forum has a great wealth of programmers.
-
Mar 5th, 2019, 07:05 AM
#15
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
Since you're receiving your data and creating the images from a background thread, you may want to use a BufferedGraphics object so that you can fill an area of the screen associated with a control quicker and can do it from the background thread so no need to invoke back to the GUI thread.
A BufferedGraphics object is made for the situation where you want to update the full image of an area of the screen repeatedly.
An example here.
-
Mar 5th, 2019, 08:30 AM
#16
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
"Im personally starting to think that 25fps to 2 pictureboxes is just too much and I should probably only draw at 4 of 5 fps "
Are you using a windows form timer? You could use a separate thread with your own timer instead of the windows form timer. First off my bet is that you will run faster and second run much smoother.
Anyhow how fast you update the screen depends on the image size. If you are full screen you can only do about 30fps on most pcs if you have anything else going on in between screen updates. How big is your image?
30 fps is about as fast as the eye can see anyway.
But what it sounds like to me from your description is not a memory leak. How do you know there is a memory leak?
Although pic2.Image.Dispose does not dispose the original image that you copied there by reference from the other thread it just disposes the reference.
No it sounds like a whatucallit cascading recursive infinate loop. Sounds like your calls are piling up and your thread id updating faster than the screen is updating and gets stacked up? Why do you say memory leak have I missed that?
Put a break in there and then look at the call history when the mb are going up and see if you have events stacked up.
But the bottom line is what are you making and how fast does it need to run? Why use a background worker at all? Are you doing something on the main gui thread besides showing the screen? It sounds like you are just monitoring your process? Why use a bgw? Maybe a normal thread or async or something is better? Or even just a timer on the main gui thread?
ANd yes I agree with Shaggy using Invalidate is better.
Refresh will wait your thread until the screen updates. That is what is wrong?? Your refresh hoses the whole thing. Makes the events stack up. Thats why removing it makes it run better.
How are you timing your fps? You should put a counter in the paint event (that what is refreshed?) and use that frame counter to see how fast it is really updating the screen. Is that how yours is set up? Then compare refresh with invalidate. Even single step debug if you can and watch where it goes may not do what you think. You may find the only thing running fast is your timer.
Thoughts for discussion.
PS if it comes to it instead of running 5 fps you could show every 3 frames and run at equivalent 15 fps etc? Does that make sense?
Last edited by tommytwotrain; Mar 5th, 2019 at 09:17 AM.
-
Mar 5th, 2019, 09:12 AM
#17
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
You should separate the receiving on the named pipe from the display of the image. In the example I posted I showed how to do that. In other words, one thread that accepts data from the pipe and another that shows the images.
I'd also make that initial buffer size of the read much much larger. Something like this (NOT TESTED)
Code:
Private RcvdImg As New Threading.AutoResetEvent(False) 'set upon receipt
Private QRcvd As New Concurrent.ConcurrentQueue(Of Bitmap) 'queue of Bitmap
Private Sub StartServerPipe()
Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
ServerPipe.WaitForConnection()
ServerPipe.WaitForPipeDrain()
Const InitBufSZ As Integer = 1024 * 1024 * 10
Dim ImageLength As Integer = 0 ' create a reader
Dim byteList As New List(Of Byte)(InitBufSZ) 'set capacity
' a continual listening loop
Do
Dim B(InitBufSZ - 1) As Byte
Dim L As Integer = ServerPipe.Read(B, 0, InitBufSZ)
If L > 0 Then
ReDim Preserve B(L - 1)
byteList.AddRange(B)
' read the pipe until the next bitmap by getting the image length in bytes
' read the three byte header to check if its a BM6 type bitmap
If B(1) = &H4D AndAlso B(0) = &H42 Then
ImageLength = BitConverter.ToInt32(B, 2)
Dim remaining As Integer = ImageLength - byteList.Count
Do While remaining > 0
L = ServerPipe.Read(B, 0, Math.Min(B.Length, remaining))
If L > 0 Then
ReDim Preserve B(L - 1)
byteList.AddRange(B)
End If
remaining = ImageLength - byteList.Count
Loop
Dim img As Byte() = byteList.ToArray()
Dim MS As New MemoryStream(img)
Dim Bmp As New Bitmap(MS)
QRcvd.Enqueue(Bmp)
RcvdImg.Set()
MS.Close()
MS.Dispose()
byteList.Clear()
img = Nothing
Else
' nothing here yet
End If
' reset/flush the byte list array
Else
'output is not a valid Bitmap!
'Throw Exception
End If
Loop While ServerPipe.IsConnected
ServerPipe.Close()
End Sub
Private Sub ShowRcvdImgs()
Do
RcvdImg.WaitOne()
While QRcvd.Count > 0
Dim Bmp As Bitmap
If QRcvd.TryDequeue(Bmp) Then
' cross thread to update the picturebox
Me.BeginInvoke(Sub()
If pic1.Image IsNot Nothing Then
pic1.Image.Dispose()
pic1.Image = Nothing
End If
If Me.chkShowImages.Checked Then
pic1.Image = Bmp
End If
If pic2.Image IsNot Nothing Then
pic2.Image.Dispose()
pic2.Image = Nothing
End If
pic2.Image = scanimage(Bmp)
pic1.Invalidate()
pic2.Invalidate()
End Sub)
' Threading.Thread.Sleep(15)'needed?
Else
RcvdImg.WaitOne(50) 'Dequeue failed
End If
End While
Loop
End Sub
Last edited by dbasnett; Mar 5th, 2019 at 10:31 AM.
-
Mar 5th, 2019, 06:05 PM
#18
Thread Starter
Member
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
Originally Posted by tommytwotrain
"Im personally starting to think that 25fps to 2 pictureboxes is just too much and I should probably only draw at 4 of 5 fps "
Are you using a windows form timer? You could use a separate thread with your own timer instead of the windows form timer. First off my bet is that you will run faster and second run much smoother.
Anyhow how fast you update the screen depends on the image size. If you are full screen you can only do about 30fps on most pcs if you have anything else going on in between screen updates. How big is your image?
30 fps is about as fast as the eye can see anyway.
But what it sounds like to me from your description is not a memory leak. How do you know there is a memory leak?
Although pic2.Image.Dispose does not dispose the original image that you copied there by reference from the other thread it just disposes the reference.
No it sounds like a whatucallit cascading recursive infinate loop. Sounds like your calls are piling up and your thread id updating faster than the screen is updating and gets stacked up? Why do you say memory leak have I missed that?
Put a break in there and then look at the call history when the mb are going up and see if you have events stacked up.
But the bottom line is what are you making and how fast does it need to run? Why use a background worker at all? Are you doing something on the main gui thread besides showing the screen? It sounds like you are just monitoring your process? Why use a bgw? Maybe a normal thread or async or something is better? Or even just a timer on the main gui thread?
ANd yes I agree with Shaggy using Invalidate is better.
Refresh will wait your thread until the screen updates. That is what is wrong?? Your refresh hoses the whole thing. Makes the events stack up. Thats why removing it makes it run better.
How are you timing your fps? You should put a counter in the paint event (that what is refreshed?) and use that frame counter to see how fast it is really updating the screen. Is that how yours is set up? Then compare refresh with invalidate. Even single step debug if you can and watch where it goes may not do what you think. You may find the only thing running fast is your timer.
Thoughts for discussion.
PS if it comes to it instead of running 5 fps you could show every 3 frames and run at equivalent 15 fps etc? Does that make sense?
Im still learning all this, its all new to me.
I had a memory leak when I was trying to update the pictureboxes. Both dbassnet and ident showed me a few things which I used and stopped the memory leak.
And yes showing every third frame makes sense. The image are 720 x 576.
-
Mar 5th, 2019, 06:06 PM
#19
Thread Starter
Member
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
Originally Posted by passel
Since you're receiving your data and creating the images from a background thread, you may want to use a BufferedGraphics object so that you can fill an area of the screen associated with a control quicker and can do it from the background thread so no need to invoke back to the GUI thread.
A BufferedGraphics object is made for the situation where you want to update the full image of an area of the screen repeatedly.
An example here.
THanks for the pointer passel, I will look at this as soon as I have time - my next few days are really busy so no time to look just yet
-
Mar 5th, 2019, 06:12 PM
#20
Thread Starter
Member
Re: [RESOLVED] Picturebox memory leak with .refresh() to redraw it.
Originally Posted by dbasnett
You should separate the receiving on the named pipe from the display of the image. In the example I posted I showed how to do that. In other words, one thread that accepts data from the pipe and another that shows the images.
I'd also make that initial buffer size of the read much much larger. Something like this (NOT TESTED)
Code:
Private RcvdImg As New Threading.AutoResetEvent(False) 'set upon receipt
Private QRcvd As New Concurrent.ConcurrentQueue(Of Bitmap) 'queue of Bitmap
Private Sub StartServerPipe()
Dim ServerPipe As New System.IO.Pipes.NamedPipeServerStream("From_FFmpeg")
ServerPipe.WaitForConnection()
ServerPipe.WaitForPipeDrain()
Const InitBufSZ As Integer = 1024 * 1024 * 10
Dim ImageLength As Integer = 0 ' create a reader
Dim byteList As New List(Of Byte)(InitBufSZ) 'set capacity
' a continual listening loop
Do
Dim B(InitBufSZ - 1) As Byte
Dim L As Integer = ServerPipe.Read(B, 0, InitBufSZ)
If L > 0 Then
ReDim Preserve B(L - 1)
byteList.AddRange(B)
' read the pipe until the next bitmap by getting the image length in bytes
' read the three byte header to check if its a BM6 type bitmap
If B(1) = &H4D AndAlso B(0) = &H42 Then
ImageLength = BitConverter.ToInt32(B, 2)
Dim remaining As Integer = ImageLength - byteList.Count
Do While remaining > 0
L = ServerPipe.Read(B, 0, Math.Min(B.Length, remaining))
If L > 0 Then
ReDim Preserve B(L - 1)
byteList.AddRange(B)
End If
remaining = ImageLength - byteList.Count
Loop
Dim img As Byte() = byteList.ToArray()
Dim MS As New MemoryStream(img)
Dim Bmp As New Bitmap(MS)
QRcvd.Enqueue(Bmp)
RcvdImg.Set()
MS.Close()
MS.Dispose()
byteList.Clear()
img = Nothing
Else
' nothing here yet
End If
' reset/flush the byte list array
Else
'output is not a valid Bitmap!
'Throw Exception
End If
Loop While ServerPipe.IsConnected
ServerPipe.Close()
End Sub
Private Sub ShowRcvdImgs()
Do
RcvdImg.WaitOne()
While QRcvd.Count > 0
Dim Bmp As Bitmap
If QRcvd.TryDequeue(Bmp) Then
' cross thread to update the picturebox
Me.BeginInvoke(Sub()
If pic1.Image IsNot Nothing Then
pic1.Image.Dispose()
pic1.Image = Nothing
End If
If Me.chkShowImages.Checked Then
pic1.Image = Bmp
End If
If pic2.Image IsNot Nothing Then
pic2.Image.Dispose()
pic2.Image = Nothing
End If
pic2.Image = scanimage(Bmp)
pic1.Invalidate()
pic2.Invalidate()
End Sub)
' Threading.Thread.Sleep(15)'needed?
Else
RcvdImg.WaitOne(50) 'Dequeue failed
End If
End While
Loop
End Sub
Thanks for doing this, as stated in another response I dont have much time at the moment and will get to look at this properly in a few days. I don't want to just copy it, I want to know whats happening.
I appears at a glance that you are queing the images in a collection to show them on another thread .... If i read it correctly then I really like this idea.
Many thanks
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|