C#触发含GDI+错误的System.Reflection.TargetInvocationException异常排查
问题解决:C# GDI+ 通用错误(ExternalException)
该异常和集合长度为23完全无关,A generic error occurred in GDI+ 90%以上的触发原因是资源未释放、文件被占用、路径权限不足三类,结合你提供的代码,具体问题和修复方案如下:
核心问题点
- 重复执行逻辑导致文件占用:你同时为
WebClient注册了DownloadFileCompleted和DownloadDataCompleted两个回调,两个回调中写了完全一致的遍历图片调用DrawText的逻辑,两类下载任务都完成时,两个回调会先后/同时执行,同一个图片文件被两个线程同时打开写入,直接触发GDI+报错 - GDI资源未显式释放:不管是
DrawText方法内加载的图片,还是下载卫星图时创建的Image对象,都没有主动释放非托管资源,GDI+资源不会被GC自动及时回收,操作到最后一个文件时资源耗尽、文件句柄未释放就会触发异常 - 回调逻辑冗余:你已经使用了
await异步任务写法,完全不需要依赖Completed事件处理后续逻辑,下载循环走完后直接执行后续绘制逻辑即可,从根源避免多回调冲突
修复步骤
- 删掉所有
DownloadFileCompleted、DownloadDataCompleted的回调注册代码,将回调内的后续逻辑直接搬到for循环下载全部完成之后,避免重复执行 - 所有
Image对象都用using包裹,确保用完立即释放资源,下载卫星图的代码修改为:
using (MemoryStream ms = new MemoryStream(await client.DownloadDataTaskAsync(new Uri(urls[i])))) using (Image img = Image.FromStream(ms, true)) { img.Save(satelliteFolderImagesDownload + "\\image" + satCounter + ".gif", System.Drawing.Imaging.ImageFormat.Gif); }
- 改造
DrawText方法,确保内部加载的图片、画布、画笔等所有GDI对象都用using包裹,同时避免保存时原文件被占用,示例如下:
public void DrawText(string text, string imagePath) { using (Image img = Image.FromFile(imagePath)) using (Graphics g = Graphics.FromImage(img)) using (Font font = new Font("Arial", 12)) using (Brush brush = Brushes.Black) { g.DrawString(text, font, brush, new PointF(10, 10)); // 先写到内存流,释放原图片对象后再覆盖文件,避免占用报错 using (MemoryStream ms = new MemoryStream()) { img.Save(ms, img.RawFormat); ms.Position = 0; using (FileStream fs = new FileStream(imagePath, FileMode.Create, FileAccess.Write)) { ms.CopyTo(fs); } } } }
- 若你需要保留Completed事件逻辑,必须加锁避免两个回调同时执行文件操作,或者只保留一个回调的处理逻辑。
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

