GDI+多线程解码JPEG随机触发内存资源不足错误如何解决
问题根因分析
你的代码存在多个核心错误,是导致随机内存不足报错的直接原因:
- TStreamAdapter 生命周期管理错误
你直接把临时创建的TStreamAdapter传给TGPBitmap构造函数,作为COM对象的TStreamAdapter没有被显式持有引用,构造结束后会被自动析构,导致TGPBitmap后续读取流数据时访问无效内存,被系统误判为内存资源不足。 - 未检查
TGPBitmap的创建结果
你没有调用GetLastStatus校验TGPBitmap是否创建成功,就直接调用GetWidth/GetHeight方法,当TGPBitmap因为流读取失败、GDI+未初始化等原因处于无效状态时,调用成员方法必然抛出异常。 - 跨线程操作VCL的TBitmap对象不符合规范
VCL的TBitmap不是线程安全对象,你仅锁定Canvas无法保护修改Width/Height/PixelFormat的操作:修改这些属性会触发内部HBITMAP的重新分配,该过程不受Canvas.Lock保护,多线程同时操作GDI对象时会出现随机资源冲突。 - 工作线程未初始化GDI+环境
GDI+要求每个使用它的线程都要单独执行初始化/销毁流程,仅在主线程初始化GDI+的情况下,工作线程调用GDI+接口会随机出现各类未定义错误,包括内存不足报错。 - TGPGraphics创建重试逻辑无意义
TGPGraphics创建失败的核心原因是传入的Canvas.Handle无效,重试无法解决根本问题,反而会增加不必要的线程阻塞。
解决方案
按照以下步骤修改代码即可解决问题:
- 修复
TStreamAdapter的引用管理,新增TGPBitmap创建结果校验,移除无意义的重试逻辑:
function LoadJPEGImageGDIPlus(lStream : TStream; LoadedImage : TBitmap; LoadEXIF : Boolean{$IFDEF LOCALTRACE}; ThreadID : WideString{$ENDIF}) : Boolean; var gdiBitmap : TGPBitmap; gfx : TGPGraphics; streamAdapter: IStream; gdiStatus : TStatus; begin Result := True; gfx := nil; gdiBitmap := nil; LoadedImage.Canvas.Lock; Try // 显式持有StreamAdapter的引用,soReference表示不接管传入的lStream生命周期 streamAdapter := TStreamAdapter.Create(lStream, soReference); gdiBitmap := TGPBitmap.Create(streamAdapter); // 校验TGPBitmap是否创建成功 if gdiBitmap.GetLastStatus <> ok then begin Result := False; Exit; end; LoadedImage.PixelFormat := pf32bit; LoadedImage.Width := gdiBitmap.GetWidth; LoadedImage.Height := gdiBitmap.GetHeight; gfx := TGPGraphics.Create(LoadedImage.Canvas.Handle); gdiStatus := gfx.GetLastStatus; If gdiStatus = ok then Begin gdiStatus := gfx.DrawImage(gdiBitmap,0,0,LoadedImage.Width,LoadedImage.Height); If gdiStatus <> ok then Begin Result := False; End; FreeAndNil(gfx); End else Begin Result := False; End; Except on E : Exception do Begin Result := False; End else Begin Result := False; End; End; If gdiBitmap <> nil then FreeAndNil(gdiBitmap); If gfx <> nil then FreeAndNil(gfx); LoadedImage.Canvas.Unlock; end;
- 在所有调用该函数的工作线程入口处新增GDI+初始化,线程退出前执行销毁:
// 线程执行入口代码 var gdiplusToken: ULONG_PTR; gdiplusInput: TGDIPlusStartupInput; begin gdiplusInput.GdiplusVersion := 1; gdiplusInput.DebugEventCallback := nil; gdiplusInput.SuppressBackgroundThread := False; gdiplusInput.SuppressExternalCodecs := False; GdiplusStartup(gdiplusToken, @gdiplusInput, nil); try // 线程原有业务逻辑,包括调用LoadJPEGImageGDIPlus finally GdiplusShutdown(gdiplusToken); end; end;
- (可选优化)避免跨线程操作UI层的TBitmap:在工作线程中创建临时TBitmap完成解码,再通过线程同步的方式将像素数据复制到UI线程的目标TBitmap中,彻底规避跨线程GDI对象操作的冲突。
内容的提问来源于stack exchange,提问作者bLight
相关产品推荐
相关产品推荐

