Long drawing loop cancellation

  • Thread starter Thread starter Alex Zhitlenok
  • Start date Start date
A

Alex Zhitlenok

Thinking about a standard "silly" problem of giving a user an option
to cancel long drawing loop, I read a lot of articles and did some
investigations of my own. (I work with C# .Net 2003).
My application must provide a very sophisticated drawing, which is
actually a millions-length sequence of simple line or ellipse drawings
executed in a very long loop. Naturally, I'd like to give a user an
option to cancel the drawing.
I have a number of rather bad solutions and I don't see any acceptable
one.
The Solutions are:
1. The drawing loop is execute in the UI thread and calls DoEvents()
to allow "Cancel" button to be pressed. The downside is well known
(see DoEvents() method adoption discussions) – UI thread is busy and
natural events sequence is broken.
2. The drawing loop is executed not in the UI thread. The downside is
seldom exceptions. Generally, as I know, .Net does not allow drawing
from non UI thread.
3. The drawing loop is executed not in the UI thread but all drawing
operations are executed in the UI thread by calling Invoke for each
elementary drawing operation. Theoretically, this must be the correct
schema! . The downside is that Invoke() is a very expensive call, so
it works inappropriately long (see table below).
4. Using memory-stream metafile for buffering. Actually, this is not
what I need; in addition, it works even longer then #3.
Here is the execution time comparative graph for different
implementations of a loop with 2,000,000 drawing lines on my PC.
1. Execution in the UI thread (without DoEvents() calling) - time
00:00:04.6250592 (given as 1) and no "Cancel" available
2. Execution in the UI thread (calling DoEvents() on each step) -
00:00:10.5937500 (2.3 times longer)
3. Execution in a non UI thread (calling Invoke() for each drawing
operation) - 00:05:41.560314 (73.8 times longer)
4. Drawing in memory-metafile (no DoEvents() or Invoke() ) - execution
time 00:05:06.7153993 (66.3 times longer)
Of course, 5 minutes instead of 5 seconds is too much for such a
common task (a long loop cancellation). Any suggestions will be deeply
appreciated.
Alex
 
CreateGraphics is safe to be called from any thread. So you don't need to
Invoke in the worker thread. You can try this:

void draw()
{
Graphics g = CreateGraphics();
Random r = new Random();
for(int i = 0; i < 100000; i ++)
g.DrawLine(Pens.Brown, r.Next(500), r.Next(500), r.Next(500),
r.Next(500));
}
private void button1_Click(object sender, System.EventArgs e)
{
System.Threading.Thread t = new System.Threading.Thread(new
ThreadStart(draw));
t.Start();
}

However, if you draw directly on a control, you are usually supposed to
draw in OnPaint. 4 seconds are too long for an OnPaint. You may need some
other optimizations.

Hope this helps.

Regards,
Xin

This posting is provided "AS IS" with no warranties, and confers no rights.

Microsoft Security Announcement: Have you installed the patch for Microsoft
Security Bulletin MS03-026? If not Microsoft strongly advises you to review
the information at the following link regarding Microsoft Security Bulletin
MS03-026 http://www.microsoft.com/security/security_bulletins/ms03-026.asp
and/or to visit Windows Update at http://windowsupdate.microsoft.com to
install the patch. Running the SCAN program from the Windows Update site
will help to insure you are current with all security patches, not just
MS03-026.
 
Use your worker thread to draw the items onto an in-memory bitmap. Use a
mutex to manage access to the bitmap and set a second timer in the main
application that draws the backbuffer to the screen every 200 milliseconds
or so. The user will then be able to cancel the redraw at any time and the
drawing will appear to progress in chunks.

--
Bob Powell [MVP]
C#, System.Drawing

Check out the GDI+ FAQ
http://www.bobpowell.net/gdiplus_faq.htm

Buy quality Windows Forms tools
http://www.bobpowell.net/xray_tools.htm

New Tips and Tricks include creating transparent controls
and how to do double buffering.
 
Thank you all who tried to help me.
1. Xin's suggestion is exactly what I started from. Actually, this is
drawing from another thread. In that case, the application
occasionally throws InvalidOperationException with "the object is
currently in use elsewhere" and I don't know how to overcome it – that
was the problem. (See the discussion about this exception)
2. Bob Powell's suggestion is to draw into a bitmap. It means, to draw
into a buffer in a separate thread and to draw the buffer onto Control
in the UI thread. Different objects could be used as an intermediate
buffer. Bob's suggestion is to use a bitmap. I used metafile (that was
the reason I talked about metafile drawing). I agree that using bitmap
could be faster then metafile; however, bitmap could not always be
used as a buffer because it occupies a huge amount (and maybe
inaccessible) of memory unlike metafile, which was mainly created to
serve as a buffer. However, drawing into metafile (as I posted in the
main message) is very slow.
By the way, I create a DLL (not an application) that means I have a
Graphics object as a parameter and, in general, no additional info
about a calling thread or Control.
Thank you,
Alex
 
Back
Top