GDI+ - How to use Graphics as parameter with multithreading

Asked By Erik Little on 15-May-13 10:04 PM
I am trying to use a Graphics object as a parameter to be used in an async function, but when my Graphics objects is to draw it is nulled out and or is broken however, you want to look at it, I no longer have access to the drawing surface that the graphics object is drawing to when I call an async function.

Is there a workaround for this, or can I pass the Graphics object as a state variable or something?

What's the trick to this mystery?

So, ya'll know, I am not drawing onto the user thread, nor am i displaying any of the Bitmaps that the Graphics object is drawing onto. All the Bitmaps that are being drawn are returned to the user by email or web, so all this magic is happening in a class Library.

  internal async static Task<DoorPoints> DrawDoorFrame(Graphics dc, IDoor door, RectangleF ownerRect,IBay bay, bool forDoorDetail = false)
  {
 return await Task.Run(() =>
    {
    bool isFirstBay = ownerRect.Left < 55,
     hasJamb = door.HasJamb,
     hasSideLites = door.HasSidelite.Value;
 
 
///OTHER stuff going on but omitted..///
 
    //
    if (bay != null) { bayLeftSl = Util.getInchPx(bay.LeftSiteline); bayRightSl = Util.getInchPx(bay.RightSiteline); };
 
    //header
    if (forDoorDetail) { yMisEqual = 2.5f; } else { /*yMisEqual = -2;*/yMisEqual = 2.5f; };

PointF[] pointsLeft =
new PointF[] { new PointF(startX, startY),
new PointF(startX, endY) },
pointsRight = new PointF[] { new PointF(endX, startY), new PointF(endX, endY) },
     pointsTop = new PointF[] { new PointF(startX, startY), new PointF(endX, startY) };
 
    using (Pen pen = ShopDrawing.Pens.GetFramePen())
    {
    //pathPen removes the bay sill under the door
    /*if bay is the jambs for this door no need for a patch; no sill is drawn when drawing bays*
if (door.HasJamb) { using (Pen patchPen = new Pen(Constants.BACK_COLOR, 10))
{ dc.DrawLine(patchPen, startX - 2, ownerRect.Bottom, endX - 2, ownerRect.Bottom); };};
 
/// other stuff going on , but omitted//



Whenever I get right here:
{ dc.DrawLine(patchPen, startX - 2, ownerRect.Bottom, endX - 2, ownerRect.Bottom); };};

it shows me in the debugger that the dc, "my graphics object that is passed as a parameter is no longer available"

How do I get around this issue? there is quite a few other function that use this same graphics object but it does not matter what order the drawing takes place nor does the user ever view the bitmaps from a windows form.



Robbe Morris replied to Erik Little on 15-May-13 10:07 PM
I've had nothing but problems trying to use the same graphics object over multiple threads.  I don't know if this will work for you but...  It might be worth considering launching a new thread that draws on a transparent background rectangle.  After all of the threads complete, you could draw each rectangle onto the master Graphics object.

Kind of a layering effect.
Erik Little replied to Robbe Morris on 15-May-13 10:45 PM
Yea, it is obvious I'm going to have to get my hands dirty with this one. DAM! Didn't really want to, Oh well, whats another day.

I am thinking about creating a object property that maybe can be shared, I don't know. Will update the final result when I get it. I do not have a choice because I'm going to have to draw many pages, "Bitmaps" and or PDF Pages into one PDF Document.

Thanks!

Erik Little replied to Robbe Morris on 16-May-13 01:26 AM
What do you this about a singleton? 

Sounds promising, what do you think?


I just wouldn't pass the graphics as a parameter, I get if from a class level function or property?

I would have to lock the graphics down per thread, but it still sounds promising.

Suggestions?
Robbe Morris replied to Erik Little on 16-May-13 08:30 AM
Ok, so you are putting together a document versus drawing a bunch of different stuff on a single bitmap.

Create a class that exposes a public instance of your bitmap or pdf page (or both depending on what you need).  Pass the data needed into the constructor including perhaps which pdf page it is (if applicable).  Then, launch a thread to render that specific object.  Launch a new thread for each one.

http://www.nullskull.com/faq/1488/threadpoolqueueuserworkitem-multithreading-code-sample.aspx

You can put them together on the main document as the thread finishes (that class object will come back in the thread completed event) and properly destroy an objects on the class from that thread.  In your case, you'll likely need to be careful about the number of threads you launch before waiting for them all to complete.  If you run out of available threads, exceptions will get thrown
Erik Little replied to Robbe Morris on 16-May-13 12:06 PM
Hey man that works perfectly.

I see what you're saying now, I was creating a new thread for the man entry function then I was trying to create new thread for each helper / sub function.

It did speed up a little bit, but my main concern is when I've got more than just 20 users at once all requesting all of their plans at once. I'm hoping implementing the threading this way will help speed up request when there is more than one.

The whole threading thing I'm still working on, but I'm starting to get it.

thanks.
Robbe Morris replied to Erik Little on 16-May-13 12:12 PM
Well, then what you'll want to do is work up a multi-threaded client console app and attempt to beat the crap out of your code.  You'll find out pretty darn quick whether this is going to work or not.
Erik Little replied to Robbe Morris on 16-May-13 12:49 PM
Out of everything for this project I'm on this is the portion that scares me the most.

I want to share to you what I've got and at least I can know that I've got the basics of what needs to be done.
I'm also going to share with you another type of async approach and it is with the using(){}. I cannot apply the await because it keeps asking for the .Result

---Here is the one that I'm assuming that is multithreading, and the one you've helped me with, and I'll tell you there is a whole hell of a lot of work going on for each one of those function call and or Task<>

public async static Task<PdfSharp.Pdf.PdfDocument> RollUpDrawingsPDF(IElevation elevation)
   {
 
     List<Bitmap> allSheets = new List<Bitmap>();
 
   //elevation allSheets.Add(await ShopDrawing.Manager.GetShopDrawing(elevation, true, RotateFlipType.Rotate90FlipNone));
 
     //door schedules, 3 schedules per sheet  allSheets.AddRange(await ShopDrawing.Door.GetDoorSecheduleSheets(elevation, RotateFlipType.Rotate90FlipNone, 3));
 
  //materials list allSheets.Add(await MaterialsList.Manager.GetMaterialList(elevation).GetDrawing());
 
  //optimized parts allSheets.Add(await Optimization.Manager.GetOptimizedParts(elevation).GetDrawing());
 
  //cut sheet allSheets.Add(await CutSheet.Manager.GetCutSheet(elevation).GetDrawing());
 
 
  return await PDFMaker.PDFManager.GetPDF(allSheets, true);
   }




----------------------Now here is a different type of scenario: This one returns one Bitmap drawn from a collection of Bitmaps and returns 1 image, unlike the one above that returns 1 PDF Document with different PDF Sheets---------------------------------------------


public async static Task<Bitmap> RollUpDrawingsImage(IElevation elevation)
  {
    int height = 0, width = 800;
    Bitmap completeDrawings = null;
 
    return await Task.Run(() =>
      { using (Bitmap elevationDoor = ShopDrawing.Merger.MergeElevationAndDoor(elevation, RotateFlipType.Rotate90FlipNone).Result)
        {
 
 using (Bitmap partsList = MaterialsList.Manager.GetMaterialList(elevation).GetDrawing().Result)
          {
 
using (Bitmap optimized = Optimization.Manager.GetOptimizedParts(elevation).GetDrawing().Result)
            {
  using (Bitmap cutSheet = CutSheet.Manager.GetCutSheet(elevation).GetDrawing().Result)
              {
 
  height = (elevationDoor.Height + optimized.Height + cutSheet.Height + partsList.Height);
                completeDrawings = new Bitmap(width, height + 40);
 
       using (var dc = Graphics.FromImage(completeDrawings))
                {
       dc.DrawImageUnscaled(elevationDoor, 0, 0);
 
       dc.DrawImageUnscaled(partsList, 0, elevationDoor.Height + 10);
 
       dc.DrawImageUnscaled(optimized, 0, (elevationDoor.Height + partsList.Height) + 20);
  dc.DrawImageUnscaled(cutSheet, 0, (elevationDoor.Height + partsList.Height + optimized.Height) + 30);
                };
              }
            }
          }
        };
        return completeDrawings;
      });
  }



**Notice for this one above that I cannot apply the await operator in from of the .GetDrawing()?
I've got  to use the .Result to get the result.
What type of effect am I having on my code? 
Should I be using the result? Or designing my code like for the RollUpDrawingsPDF()  function?


I know I'm banging you up for questions, but this is one of the most important parts of the last two years of work I've put into this one app. So, I'm very grateful for your help!


Erik