locked
A generic error occurred in GDI+ when using multi threading RRS feed

  • 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: 

    https://social.msdn.microsoft.com/Forums/sqlserver/en-US/40dff531-572a-459a-9f96-a911981d7a84/a-generic-gdi-error-occurs-in-parallelfor?forum=parallelextensions  

    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