I seem to be getting a memory leak when I pass a BytesIO object (containing and animated GIF) from a helper thread using wx.CallAfter to update a wx.AnimationCtrl in the main thread. I’ve posed some sample code that generates the problem. Everything works fine if I save the gif to a file, pass the file name to main as a string, and then update the animation control from the file. With a BytesIO object, the memory just continues to go up in several MB chunks with each update. I’ve also tried destroying and creating the animation control for each new update, to no effect. In the actual application I am downloading images and would like to do all the updates in memory rather than write files. I am doing this on a RaspberryPI 3 model B so there is not a lot of spare memory to play with.
Any thoughts on what I am doing wrong here?
type or paste code here
#!/usr/bin/env python
import wx
from wx.adv import Animation, AnimationCtrl
import threading
from PIL import Image
import io
class AnimatedGIFThread(threading.Thread):
def __init__(self, window):
threading.Thread.__init__(self)
self._want_abort = 0
self.window = window
self.timeRefresh = threading.Event()
self.timeRefresh.clear()
self.timeDelay = 10 # Time (seconds) between updates.
def run(self):
images_lst = [None]*3
duration_lst = [250]*3
for i in range(3):
fpath = '/home/pi/python/source/testing/testimage' + str(i) + '.png'
images_lst[i] = Image.open(fpath)
while True: # Check for abort
if self._want_abort:
return
animated_gif = self.BuildAnimatedGIF(images_lst, duration_lst)
wx.CallAfter(self.window.UpdateGIF, animated_gif)
self.timeRefresh.wait(self.timeDelay) # Delay between updates
def BuildAnimatedGIF(self, i_lst, d_lst):
a_gif = io.BytesIO()
a_gif.seek(0)
i_lst[0].save(a_gif,
format='GIF',
save_all=True,
append_images=i_lst[1:],
duration=d_lst,
loop=0)
a_gif.seek(0,2)
print('GIF image size = ', a_gif.tell())
return(a_gif)
def abort(self):
self._want_abort = True
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class MainFrame(wx.Frame):
def __init__(self):
# Frame with no border
wx.Frame.__init__(self, None, title="Animated GIF Test", pos=(0,0), size=(460,460))
# Create a panel
self.panel = wx.Panel(self, -1)
# Animated gif control
self.agif = AnimationCtrl(self.panel,
wx.ID_ANY,
anim=wx.adv.NullAnimation,
pos=(40,40),
size=(420,360),
style=wx.adv.AC_DEFAULT_STYLE)
# Event bindings with no calling objects
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
# Start thread
self.thread_gif = AnimatedGIFThread(self)
self.thread_gif.start()
def OnCloseWindow(self, event):
try:
self.thread_gif.abort()
self.thread_gif.stop()
except: pass
self.Destroy()
def UpdateGIF(self, a_gif):
a_gif.seek(0)
self.agif.Load(a_gif)
self.agif.Play()
if __name__ == '__main__':
app = wx.App()
frame = MainFrame()
frame.Show(True)
app.MainLoop()