Parallel.For中实例化Bitmap偶发无效参数异常求助
解决Parallel.For下创建Bitmap时的无效参数异常问题
我帮你捋捋这个问题哈,你遇到的核心问题是多线程环境下GDI+资源的线程安全问题,再加上内存碎片化和资源泄漏,才导致创建Bitmap时抛出无效参数异常。下面一步步给你拆解原因和修复方案:
为啥会出问题?
- GDI+天生不支持多线程并发:
Bitmap、Graphics这些类都依赖GDI+,而GDI+不是线程安全的。你在Parallel.For里同时跑多个全景图生成任务,还在每个任务里嵌套Parallel.For下载图片,多个线程同时操作GDI资源,很容易触发资源竞争,导致创建Bitmap失败。 - Image资源没释放干净:原来的代码里,
images[,]数组存了一堆Image对象,用完之后没调用Dispose(),会导致GDI句柄泄漏。系统的GDI资源是有限的,耗完之后新的Bitmap就创建不了了。 - 过度并行导致内存碎片化:每个全景图要创建一个88MB左右的大Bitmap(26512 ×13512),如果同时跑多个任务,就算总内存够,内存也会被拆成很多小碎片,找不到连续的内存块分配给新Bitmap,自然就抛异常了。
- 循环创建Graphics浪费资源:原来的代码里每次DrawImage都创建一个Graphics对象,这完全没必要,还会额外消耗GDI资源。
怎么修复?
1. 控制并发度,别过度并行
用ParallelOptions.MaxDegreeOfParallelism限制同时运行的任务数,比如根据CPU核心数或者实际情况设个合理的值(比如4),避免瞬间占用过多资源。
2. 确保所有GDI资源都及时释放
所有Image、Bitmap、Graphics、WebClient这些资源,全都用using包裹,确保用完自动释放,别留尾巴。
3. 下载一个绘制一个,别存一堆Image
不用二维数组存所有小图,下载一个就直接画到大图上,画完就释放,这样能大幅减少内存占用和资源泄漏。
修改后的代码示例
public static void AllPanoramas((double Lat, double Lon)[] locData, string folderPath, ImageFormat format) { ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount * 12; string[] panoIDs = new string[locData.Length]; // 控制获取panoID的并发数,别一下开太多线程 Parallel.For(0, locData.Length, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }, i => { panoIDs[i] = Web.GetPanoID(locData[i], 50); }); // 控制全景图生成的并发数,避免同时创建太多大Bitmap Parallel.For(0, panoIDs.Length, new ParallelOptions { MaxDegreeOfParallelism = 4 }, i => { // 用Path.Combine替代字符串拼接,更安全 Panorama(panoIDs[i], Path.Combine(folderPath, $"image{i}.{format.ToString().ToLower()}"), format); }); } public static void Panorama(string panoID, string file, ImageFormat format) { // 只创建一次大Bitmap和Graphics,全程复用 using (Bitmap result = new Bitmap(26 * 512, 13 * 512)) using (Graphics g = Graphics.FromImage(result)) { // 单线程下载绘制,避免GDI冲突,也能减少内存压力 for (int x = 0; x < 26; x++) { for (int y = 0; y < 13; y++) { // 每个小图的资源都用using包裹,用完自动释放 using (WebClient client = new WebClient()) using (MemoryStream ms = new MemoryStream(client.DownloadData(Get.TileURL(panoID, x, y)))) using (Image tile = Image.FromStream(ms)) { g.DrawImage(tile, x * 512, y * 512); } } } result.Save(file, format); } }
额外优化小建议
- 换用HttpClient代替WebClient:WebClient是比较老的类,HttpClient更适合多线程场景,能复用连接池,减少资源消耗。
- 监控GDI资源:打开任务管理器,切换到“详细信息” tab,右键添加“GDI对象”列,看看程序运行时这个数值会不会持续上涨,如果涨了说明还有资源没释放干净。
- 调整并发数:
MaxDegreeOfParallelism的值可以根据你的机器配置调整,比如内存大的话可以设高一点,内存小就设低一点,找到适合你机器的平衡点。
内容的提问来源于stack exchange,提问作者TrueCP5
相关产品推荐
相关产品推荐

