Asked by:
A generic error occurred in GDI+ when using multi threading

Question
-
User680513913 posted
I have a program that get user int input "1" and increment it based on the amount of files in a directory then stamp that int on each file( first is 1 and so on 1++). The foreach loop go in each directory gets its files, increment the input and call the stamp method until all files are done. In this process the order is important. However multitasking ( Parallel.ForEach) does't always guarantee order, in my understanding it returns which ever thread done first and maybe also damage the i++ functionality ( correct me if I'm wrong).
The question is how to apply multi threading in this case? i am thinking save the values of the foreach at the end, pass it to the stamping method and have the method stamp x amount of files at a time. I don't know if its possible or how to apply.
Here is my watermark method:
public void waterMark(string text, string sourcePath, string destinationPathh)
{
try
{
using (Bitmap bitmap = new Bitmap(sourcePath))
{
int compressionTagIndex = Array.IndexOf(bitmap.PropertyIdList, 0x103);
PropertyItem compressionTag = bitmap.PropertyItems[compressionTagIndex];
byte[] com = compressionTag.Value;
Encoder encoder = Encoder.Compression;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(encoder, (long)EncoderValue.CompressionCCITT4);
myEncoderParameters.Param[0] = myEncoderParameter;
ImageCodecInfo myImageCodecInfo;
myImageCodecInfo = GetEncoderInfo("image/tiff");
Brush brush = new SolidBrush(Color.Black);
Font font = new Font("Arial", 50, FontStyle.Italic, GraphicsUnit.Pixel);
Bitmap tempBitmap = new Bitmap(bitmap.Width, bitmap.Height);
Graphics tempGraphics = Graphics.FromImage(tempBitmap);
SizeF textSize = tempGraphics.MeasureString(text, font);
tempBitmap = new Bitmap(bitmap.Width, bitmap.Height + (int)textSize.Height + 10);
tempBitmap.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(tempBitmap))
{
graphics.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height + 100);
graphics.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
Point position = new Point(bitmap.Width - ((int)textSize.Width + 200), bitmap.Height + 5);
//Point position = new Point((int)((bitmap.Width - textSize.Width) / 2), bitmap.Height + 5);
graphics.DrawString((text), font, brush, position);
if (new[] { 2, 3, 4 }.Contains(com[0]))
{
bitmap.Dispose();
tempBitmap.Dispose();
tempBitmap.Save(destinationPathh, /*ImageFormat.Tiff,*/ myImageCodecInfo, myEncoderParameters);
return;
}
bitmap.Dispose();
tempBitmap.Dispose();
tempBitmap.Save(destinationPathh, ImageFormat.Tiff);
}
}
}
The foreach loop:
var files = folder.GetFiles(); Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (file, state,indexer) => { //somecode that calls the waterMark method in multiple spots as of now });
Tuesday, January 21, 2020 6:26 PM
All replies
-
User753101303 posted
Hi,
It always happen on Save ? You are really using tempBitmap or do you mean bitmap ?
Tuesday, January 21, 2020 6:46 PM -
User680513913 posted
I need to use tempbitmap because my original image type is tif. As a work around to solve the issue I used:
if (new[] { 2, 3, 4 }.Contains(com[0])) { bitmap.Dispose(); tempBitmap.Dispose(); tempBitmap.Save(destinationPathh, /*ImageFormat.Tiff,*/ myImageCodecInfo, myEncoderParameters); return; } bitmap.Dispose(); tempBitmap.Dispose(); tempBitmap.Save(destinationPathh, ImageFormat.Tiff);
However, I feel like this is logically wrong to dispose before saving. I don't know the final results because I came across other issues due to multi threading. That's why I am trying to find a way to maybe call the watermark after the foreach finish working and just stamp many images at a time with the correct order... thank you
Tuesday, January 21, 2020 7:18 PM -
User680513913 posted
my original image is tif and i need tempbitmap to workaround some issues. As a workaround for the current issue I added dispose before save
However, I feel like it is logically wrong because i am disposing before saveing. I can't see output yet due to other errors related to multi threading such as file already exists. This is why I am thinking if there is a way to finish the foreach then apply multi threading to the watermark method
Tuesday, January 21, 2020 7:25 PM -
User475983607 posted
Use a lock to serialize access to the count variable.
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement
Your code is a bit confusing as it is not clear where tempBitmap is set. The waterMark method has a text input but it is never used.
//text comes from the foreach already set. public void waterMark(string text, string sourcePath, string destinationPathh) { using (Bitmap bitmap = new Bitmap(sourcePath)) { //somecode using (Graphics graphics = Graphics.FromImage(tempBitmap)) { //somecode tempBitmap.Save(destinationPathh, ImageFormat.Tiff); //Erroe^: a generic error occurred in gdi+ //I think due to trying to save multiple files at once } } }
You stated order matters but you have not defined the order. Is the order defined when the file is processed? Or by a file attribute like create date or name?
Tuesday, January 21, 2020 7:34 PM -
User680513913 posted
I tried this:
I included the full method in the new edit.
text input comes from the foreach and increment also comes from foreach therefore all what the watermark do is stamp the files.
Order matters because the stamp is like page numbering: i++ this happens in the foreach and get passed to the watermark thus i was thinking can i add multithreading to the watermark method since its the most costly.
Thanks
Tuesday, January 21, 2020 7:49 PM -
User475983607 posted
I still do not understand how you determine order. Here's a basic example that uses a dictionary to store an index and file path if you do not like the thread safe counter idea.
int i = 0; Dictionary<int, string> fileDic = Directory.GetFiles(@"C:\Temp").ToDictionary(k => i++, t => t.ToString()); //Test foreach(var k in fileDic) { Console.WriteLine($"{k.Key} {k.Value}"); }
Tuesday, January 21, 2020 7:59 PM -
User680513913 posted
sorry, based on file name. 01, 02 etc. So it will be sorted when you open the result. Also, the source file names are in same format i++. Therefor, stamp file on with user input and the rest with user input++
Tuesday, January 21, 2020 8:04 PM -
User475983607 posted
AskAlotLearnAlot
sorry, based on file name. 01, 02 etc. So it will be sorted when you open the result. Also, the source file names are in same format i++. Therefor, stamp file on with user input and the rest with user input++
Still not clear. I think you are using alpha sort order as a join to another list which not a deterministic approach. A hash might be better but I have no idea how the files are generated.
Tuesday, January 21, 2020 8:26 PM -
User680513913 posted
source path contains the files that are named in int++ way that's how its sorted.
Tuesday, January 21, 2020 8:51 PM -
User753101303 posted
Still the same error ? Dispose is when you are entirely done with something so you should use Save before Dispose. Make sure to not save to a file name for which you have an opened bitmap (this why you tried to Dispose and Save, it should be the OTHER bitmap that you should perhaps dispose).
I would also suggest do that in two steps. For now I'm not convinced a non parallel loop would even work.
So make sure first the whole logic is fine and only once you have something that work (and if you process enough files) use a parallel loop instead and see if it still works...
Wednesday, January 22, 2020 1:54 PM -
User680513913 posted
As of now I fixed it by implementing multi threading in a different way in the main method(higher level method) like this:
//Before
helper.waterMark(text + helper.getformattedString(bates), file.FullName, resultDirectory + @"\" + helper.getformattedString(fileName) + ".tif");
//After:
string newFileName = helper.getformattedString(fileName);
string newBate = helper.getformattedString(bates);
Thread newThread = new Thread(() => helper.waterMark(text + newBate, file.FullName, resultDirectory + @"\" + newFileName + ".tif"));
newThread.Start();
I took out the dispose and kept the wateMark method as is. This improved performance by only one minute. However it kept all the logic as needed. I think since I am CPU, IO and order depended my performance will always be slow unless you guys have better way. My biggest concern is server time out when I release it. Maybe I have to switch to winforms I hope not.
Wednesday, January 22, 2020 4:27 PM