@@ -52,6 +52,7 @@ sealed class ImagePrefetcher<T> : IImageLoader
5252 private readonly T hostingPart ;
5353 private readonly IWebRequest resourceLoader ;
5454 private readonly HtmlImageInfoCollection prefetchedImages ;
55+ private readonly object lockObject = new object ( ) ;
5556
5657
5758 /// <summary>
@@ -76,8 +77,12 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
7677 /// </summary>
7778 public async Task < HtmlImageInfo ? > Download ( string imageUri , CancellationToken cancellationToken )
7879 {
79- if ( prefetchedImages . Contains ( imageUri ) )
80- return prefetchedImages [ imageUri ] ;
80+ // Check if image is already cached using thread-safe operation
81+ lock ( lockObject )
82+ {
83+ if ( prefetchedImages . Contains ( imageUri ) )
84+ return prefetchedImages [ imageUri ] ;
85+ }
8186
8287 HtmlImageInfo ? iinfo ;
8388 if ( DataUri . IsWellFormed ( imageUri ) ) // data inline, encoded in base64
@@ -89,8 +94,18 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
8994 iinfo = await DownloadRemoteImage ( imageUri , cancellationToken ) . ConfigureAwait ( false ) ;
9095 }
9196
97+ // Add to cache using thread-safe operation
9298 if ( iinfo != null )
93- prefetchedImages . Add ( iinfo ) ;
99+ {
100+ lock ( lockObject )
101+ {
102+ // Double-check pattern to prevent duplicate adds during concurrent access
103+ if ( ! prefetchedImages . Contains ( imageUri ) )
104+ {
105+ prefetchedImages . Add ( iinfo ) ;
106+ }
107+ }
108+ }
94109
95110 return iinfo ;
96111 }
@@ -121,7 +136,16 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
121136 return null ;
122137 }
123138
124- var ipart = hostingPart . AddImagePart ( type ) ;
139+ // Generate a unique GUID-based relationship ID for the image part
140+ string relationshipId = "img_" + Guid . NewGuid ( ) . ToString ( "N" ) ;
141+
142+ // Synchronize access to AddImagePart to prevent concurrent modifications to the OpenXml document
143+ ImagePart ipart ;
144+ lock ( lockObject )
145+ {
146+ ipart = hostingPart . AddImagePart ( type , relationshipId ) ;
147+ }
148+
125149 Size originalSize ;
126150 using ( var outputStream = ipart . GetStream ( FileMode . Create ) )
127151 {
@@ -131,7 +155,13 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
131155 originalSize = GetImageSize ( outputStream ) ;
132156 }
133157
134- return new HtmlImageInfo ( src , hostingPart . GetIdOfPart ( ipart ) ) {
158+ string partId ;
159+ lock ( lockObject )
160+ {
161+ partId = hostingPart . GetIdOfPart ( ipart ) ;
162+ }
163+
164+ return new HtmlImageInfo ( src , partId ) {
135165 TypeInfo = type ,
136166 Size = originalSize
137167 } ;
@@ -147,7 +177,16 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
147177 {
148178 Size originalSize ;
149179 knownContentType . TryGetValue ( dataUri ! . Mime , out PartTypeInfo type ) ;
150- var ipart = hostingPart . AddImagePart ( type ) ;
180+ // Generate a unique GUID-based relationship ID for the image part
181+ string relationshipId = "img_" + Guid . NewGuid ( ) . ToString ( "N" ) ;
182+
183+ // Synchronize access to AddImagePart to prevent concurrent modifications to the OpenXml document
184+ ImagePart ipart ;
185+ lock ( lockObject )
186+ {
187+ ipart = hostingPart . AddImagePart ( type , relationshipId ) ;
188+ }
189+
151190 using ( var outputStream = ipart . GetStream ( FileMode . Create ) )
152191 {
153192 outputStream . Write ( dataUri . Data , 0 , dataUri . Data . Length ) ;
@@ -156,7 +195,13 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
156195 originalSize = GetImageSize ( outputStream ) ;
157196 }
158197
159- return new HtmlImageInfo ( src , hostingPart . GetIdOfPart ( ipart ) ) {
198+ string partId ;
199+ lock ( lockObject )
200+ {
201+ partId = hostingPart . GetIdOfPart ( ipart ) ;
202+ }
203+
204+ return new HtmlImageInfo ( src , partId ) {
160205 TypeInfo = type ,
161206 Size = originalSize
162207 } ;
0 commit comments