Populate an ImageList from another thread

P

Paul E Collins

I want to fill an ImageList with bitmaps for a ListView from another
thread, because it's a time-consuming process. I expect the
ListViewItems' images to "load" one by one, as in a Web browser.

I wrote the following code, but the form freezes up while
CreateTileBitmaps is running, just as if I'd done it on the main
thread. How can I add items to the ImageList without this problem?
(Note: I do *not* want to use an owner-draw ListView and Paint events,
because painting the extra things like captions and focus rectangles
is too tiresome.)


private void FindTileForm_Load(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(CreateTileBitmaps));
}


private void CreateTileBitmaps(object notUsed)
{
foreach (Tile t in _tiles.Tiles)
{
Bitmap bmp = new Bitmap(32, 32);
// ... do some drawing ...

lvwMatches.Invoke(
(MethodInvoker) delegate { imlMatches.Images.Add(bmp); }
);
}
}
 
P

Peter Duniho

I want to fill an ImageList with bitmaps for a ListView from another
thread, because it's a time-consuming process. I expect the
ListViewItems' images to "load" one by one, as in a Web browser.

I wrote the following code, but the form freezes up while
CreateTileBitmaps is running, just as if I'd done it on the main
thread. How can I add items to the ImageList without this problem?

Well, you could do it correctly. That would work.

There's nothing obvious in the code that you posted that's wrong, which
means you didn't post the code that's wrong. But you must have some code
somewhere that's wrong, otherwise it would be fine.

You should post a concise-but-complete sample of code that reliably
reproduces the problem. In the meantime, see below for an example of code
that _does_ work (I put a 2-second delay in between item additions to make
it easier to watch).

Pete


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace TestListViewWorkerSingleFile
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}

public class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void _InitListView(object obj)
{
for (int iitem = 0; iitem < 10; iitem++)
{
Bitmap bmpItem = new Bitmap(32, 32);
string strItem = iitem.ToString();

Thread.Sleep(2000);

using (Graphics gfx = Graphics.FromImage(bmpItem))
{
using (Font font = new Font(Font.FontFamily,
Font.SizeInPoints * 1.1f, FontStyle.Bold))
{
SizeF szf = gfx.MeasureString(strItem, font);
StringFormat format = new StringFormat();

format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
gfx.DrawString(strItem, font, Brushes.Black, new
RectangleF(0, 0, 32, 32), format);
}
}

Invoke((MethodInvoker)delegate()
{ listView1.LargeImageList.Images.Add(strItem, bmpItem); });

Invoke((MethodInvoker)delegate()
{ listView1.Items.Add(strItem, strItem); });
}
}

private void Form1_Load(object sender, EventArgs e)
{
listView1.LargeImageList = new ImageList();
listView1.LargeImageList.ImageSize = new Size(32, 32);

ThreadPool.QueueUserWorkItem(_InitListView);
}

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.listView1 = new System.Windows.Forms.ListView();
this.SuspendLayout();
//
// listView1
//
this.listView1.Anchor =
((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.listView1.Location = new System.Drawing.Point(13, 13);
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(267, 241);
this.listView1.TabIndex = 0;
this.listView1.UseCompatibleStateImageBehavior = false;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.listView1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

}

#endregion

private System.Windows.Forms.ListView listView1;
}
}
 
P

Paul E Collins

Peter Duniho said:
Well, you could do it correctly. That would work.

Thanks for your code sample and meaningless sneering.

Your sample has the same problem with locking up if I remove the
Thread.Sleep call, and my original code works acceptably with a
Thread.Sleep call, so that seems to be the issue. I don't understand
why it is required.

I also noticed that ImageList.Items.AddRange is a *lot* faster than
several calls to Add, so I'll pass the bitmaps in groups instead of
one at a time.

Eq.
 
P

Peter Duniho

Thanks for your code sample and meaningless sneering.

There was no "sneering" in my post.
Your sample has the same problem with locking up if I remove the
Thread.Sleep call, and my original code works acceptably with a
Thread.Sleep call, so that seems to be the issue. I don't understand
why it is required.

I don't either. Mine works fine on my computer with or without the call
to Sleep().

In any case, you still haven't posted a code sample that would allow
anyone to help advise you. Given the apparent difference in behavior with
respect to the computer, you should probably also be specific about what
version of .NET you have installed and what OS you're using. Without all
of that, I don't think you're going to get a real answer.

Pete
 
G

Greg Cadmes

I want to fill an ImageList with bitmaps for a ListView from another
thread, because it's a time-consuming process. I expect the
ListViewItems' images to "load" one by one, as in a Web browser.

I wrote the following code, but the form freezes up while
CreateTileBitmaps is running, just as if I'd done it on the main
thread. How can I add items to the ImageList without this problem?
(Note: I do *not* want to use an owner-draw ListView and Paint events,
because painting the extra things like captions and focus rectangles
is too tiresome.)

private void FindTileForm_Load(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(CreateTileBitmaps));

}

private void CreateTileBitmaps(object notUsed)
{
foreach (Tile t in _tiles.Tiles)
{
Bitmap bmp = new Bitmap(32, 32);
// ... do some drawing ...

lvwMatches.Invoke(
(MethodInvoker) delegate { imlMatches.Images.Add(bmp); }
);
}



}- Hide quoted text -

- Show quoted text -


You can't simply call lvwMatches.Invoke. Try lvwMatches.BeginInvoke.
Fire and forget...

Greg
 
P

Peter Duniho

You can't simply call lvwMatches.Invoke. Try lvwMatches.BeginInvoke.
Fire and forget...

That's not a fix. It's a work-around, leaving unfixed whatever bug is
actually there.

It may well be that the bug that's there is in .NET or has something to do
with his system configuration. But it's important to understand the
difference.

Pete
 
G

Greg Cadmes

That's not a fix. It's a work-around, leaving unfixed whatever bug is
actually there.

It may well be that the bug that's there is in .NET or has something to do
with his system configuration. But it's important to understand the
difference.

Pete

Hi Pete,

Becase your listview was created by your form's gui thread, by saying
Control.Invoke
is the same as ListView.Items.Add.... syncrhonously. The calling
thread still needs to wait for the Invoke() function to complete until
the control is returned to the calling thread.


This is not a .NET bug. I use the APM (Asynchronous Programming Model)
in almost every project I work on.
Take a look at this example of asynchronous method invocation:
https://secure.codeproject.com/KB/cs/AsyncMethodInvocation.aspx

I'm sure you'll see that this is the right approach for the creating
the tiled bitmaps.

Greg
 
P

Peter Duniho

Becase your listview was created by your form's gui thread, by saying
Control.Invoke
is the same as ListView.Items.Add.... syncrhonously. The calling
thread still needs to wait for the Invoke() function to complete until
the control is returned to the calling thread.

Yes, it is a synchronous call. But as long as the main GUI thread isn't
waiting on the thread that is trying to make the synchronous call, it's
not a problem.

Conversely, if it _is_ a problem to make the synchronous call, then there
is an underlying problem with the design of the code that is likely fixed
in a better way. There are good reasons to use BeginInvoke() instead of
Invoke(), but working around a deadlocking design bug isn't one of them.

If you look at the sample code I provided to the OP (contrary to your
apparent misunderstanding, I'm not the OP, nor do I have code that isn't
working correctly), it is quite possible and reasonable to use Invoke()
rather than BeginInvoke().
This is not a .NET bug.

You don't know that. There's no way for you to know that, because you
haven't seen the code that doesn't work, and neither have I.

Furthermore, the fact that code that is known to be correct (see the
sample I posted) apparently does not work on the OP's installation
suggests that at the very least, there's something wrong with his
installation. And it may well be that whatever's wrong with his
installation is simply due to the specific version he has, and that there
is indeed a bug in that version of .NET.
I use the APM (Asynchronous Programming Model)
in almost every project I work on.
Take a look at this example of asynchronous method invocation:
https://secure.codeproject.com/KB/cs/AsyncMethodInvocation.aspx

I'm sure you'll see that this is the right approach for the creating
the tiled bitmaps.

While I'm very familiar with asynchronous programming, both in the context
of .NET and without, I disagree that this particular situation is one in
which using an asynchronous invoke is required or even desirable.

In situations where there _is_ some kind of potential deadlock problem,
switching to BeginInvoke() typically results in all of the operations
being queued up until some later time. Because it's a situation that
otherwise would deadlock, that "later time" usually winds up being "not
until the background thread has completed its work", which means that
those updates all come at once, after the background thread is done.

In other words, using BeginInvoke() winds up accomplishing pretty much the
same thing that would happen as if the work were all done synchronously in
the main thread.

Now, I don't know about you, but IMHO it doesn't really make much sense to
use a background worker thread when the net effect winds up being
identical to that had one just done everything synchronously in the main
GUI thread. That's a pointless use of background worker threads.

Pete
 
G

Greg Cadmes

Yes, it is a synchronous call. But as long as the main GUI thread isn't
waiting on the thread that is trying to make the synchronous call, it's
not a problem.

Conversely, if it _is_ a problem to make the synchronous call, then there
is an underlying problem with the design of the code that is likely fixed
in a better way. There are good reasons to use BeginInvoke() instead of
Invoke(), but working around a deadlocking design bug isn't one of them.

If you look at the sample code I provided to the OP (contrary to your
apparent misunderstanding, I'm not the OP, nor do I have code that isn't
working correctly), it is quite possible and reasonable to use Invoke()
rather than BeginInvoke().


You don't know that. There's no way for you to know that, because you
haven't seen the code that doesn't work, and neither have I.

Furthermore, the fact that code that is known to be correct (see the
sample I posted) apparently does not work on the OP's installation
suggests that at the very least, there's something wrong with his
installation. And it may well be that whatever's wrong with his
installation is simply due to the specific version he has, and that there
is indeed a bug in that version of .NET.



While I'm very familiar with asynchronous programming, both in the context
of .NET and without, I disagree that this particular situation is one in
which using an asynchronous invoke is required or even desirable.

In situations where there _is_ some kind of potential deadlock problem,
switching to BeginInvoke() typically results in all of the operations
being queued up until some later time. Because it's a situation that
otherwise would deadlock, that "later time" usually winds up being "not
until the background thread has completed its work", which means that
those updates all come at once, after the background thread is done.

In other words, using BeginInvoke() winds up accomplishing pretty much the
same thing that would happen as if the work were all done synchronously in
the main thread.

Now, I don't know about you, but IMHO it doesn't really make much sense to
use a background worker thread when the net effect winds up being
identical to that had one just done everything synchronously in the main
GUI thread. That's a pointless use of background worker threads.

Pete



Interesting...
Assuming there is nothign wrong with Paul's installation...

Before I begin, I am using VS2005 SP2 for the code you provided.

FWIW, i said it's not a Framework bug simply because the production
applications I've worked on also load large amounts of data, which
allows the end user to pause or stop the loading process, up to and
including rollbacks. Now i must say that i did not use the.net 2.0
BackGroundWorker user control. I'm not impressed with this control
whatsoever.
"...I disagree that this particular situation is one in
which using an asynchronous invoke is required or even desirable."

As I said before, you cannot expect a control created by the main
form's GUI thread, to NOT lock the form up by saying "Control.Invoke".
All this does, (and your code in particular) is provide an anonymous
delegate that prevents threading exceptions when updating the
control's UI. Additionally, expecing the main form's GUI thread to not
be waiting on anything when saying Control.Invoke from (of all places
in code) the main form's Form_Load() is simply and foolishly trusting
that all the tasks currently required (whatever they may be) are
finished. Syncrhonously speaking that is...

"> In situations where there _is_ some kind of potential deadlock
problem,
switching to BeginInvoke() typically results in all of the operations
being queued up until some later time. Because it's a situation that
otherwise would deadlock, that "later time" usually winds up being "not
until the background thread has completed its work", which means that
those updates all come at once, after the background thread is done."

Deadlocks are typically a result of programmer's logical errors.
...There are good reasons to use BeginInvoke() instead of
Invoke(), but working around a deadlocking design bug isn't one of them.

I wasn't suggesting to use BeginInvoke() to fix a deadlock issue (or
even bug as you might think), rather, I just wanted to point out that
Control.Invoke is widely misused and misunderstood, in that, it is
sometimes assumed that Control.Invoke will free up the main GUI
thread, when in fact, it does not.

I took your code and moved & replaced the synchronous call (with
async) ThreadPool.QueueUserWorkItem(_InitListView);
in Form_Load() and moved it to the main form's constructor; after the
InitializeComponent() method call; the difference was, I created a
small async class, implemented the Command pattern (i'e. Execute()),
and loaded 500 bitmap images(all the same 32x32). The result, no
deadlocks, no MDA's, no freezing, simply free to the end user.

Now of course my helper async class provided 3 delegates with 3
events.
1)LoadBitmapProgressDelegate
2)LoadBitmapErrorDelegate
3)LoadBitmapCompletedDelegate

After subscriptions and so forth, this helper class messaged the main
form with the progress, any errors, and of course, when the loading
was finished.

I didn't mean to insult you when I boasted about my APM experience,
it's just that I've spent a great deal of time perfecting these sort
of programming hurdles, and will gladly provide my code samples if
anyone needs or wants them.

At first, I did think you were the originator of this thread... it was
late, I was tired...etc. My apologies.
Hopfully Paul will benifit from reading my final thread response.

Greg
 
P

Peter Duniho

[...]
FWIW, i said it's not a Framework bug simply because the production
applications I've worked on also load large amounts of data, which
allows the end user to pause or stop the loading process, up to and
including rollbacks.

I don't see the connection. How can your own experience with other
implementations guide you in making a statement about what's going on with
the OP's computer?
Now i must say that i did not use the.net 2.0
BackGroundWorker user control. I'm not impressed with this control
whatsoever.

"This control" being the BackgroundWorker class? It's not a control, by
the way. And I find it to be a very useful class, serving the needs of a
wide variety of simple background tasks.

Maybe "impressed" wouldn't be how I'd describe it, but I don't feel a need
to be impressed by something to find it useful. Or are you using "not
impressed" as an ambiguous way of saying you don't actually like it? If
not, why don't you like it? What's wrong with it?
As I said before, you cannot expect a control created by the main
form's GUI thread, to NOT lock the form up by saying "Control.Invoke".

Of course you can. It happens all the time. When used properly,
Control.Invoke() will not "lock the form up".
All this does, (and your code in particular) is provide an anonymous
delegate that prevents threading exceptions when updating the
control's UI.

Um, no. The cross-thread exception occurs to alert the developer to an
inappropriate, cross-thread call to a control class's member. Yes, using
Invoke() (and BeginInvoke()) prevents the exception, but that's hardly
"all this does".
Additionally, expecing the main form's GUI thread to not
be waiting on anything when saying Control.Invoke from (of all places
in code) the main form's Form_Load() is simply and foolishly trusting
that all the tasks currently required (whatever they may be) are
finished. Syncrhonously speaking that is...

It's not foolish at all. Correctly written, the main GUI thread will
_never_ spend any significant time doing any one task. That's the whole
point of using the asynchronous techniques. By putting lengthy operations
elsewhere, you ensure that the main GUI thread is always ready and
available for the quick, event-driven things that the main GUI thread is
supposed to be doing.

Furthermore, with respect to your suggestion to use BeginInvoke(),
again...if the main GUI thread is stuck doing something else, the
operations started by BeginInvoke() aren't going to actually happen until
the main GUI thread is done. Yes, using BeginInvoke() will prevent the
calling thread from blocking, and yes this can in fact workaround a
deadlock problem. But it doesn't fix the underlying problem: the main GUI
thread isn't supposed to ever be stuck in the first place.
"> In situations where there _is_ some kind of potential deadlock
problem,

Deadlocks are typically a result of programmer's logical errors.

I agree. In fact, I'd go so far as to say they are _always_ the result of
_some_ programmer's logical errors. Correctly written code will never
deadlock.

So, given that...why would you suggest sweeping under the rug a logical
error that causes deadlock, by using BeginInvoke() rather than by fixing
the underlying problem causing the deadlock in the first place?
I wasn't suggesting to use BeginInvoke() to fix a deadlock issue (or
even bug as you might think), rather, I just wanted to point out that
Control.Invoke is widely misused and misunderstood, in that, it is
sometimes assumed that Control.Invoke will free up the main GUI
thread, when in fact, it does not.

Well, a) this is the first you've said anything of the sort, and b) I
haven't seen any sign that anyone is under the misconception that
"Control.Invoke will free up the main GUI thread". That'd be a rather
strange thing for anyone to think, given that by design, Control.Invoke()
specifically causes something on the main GUI thread to happen, and
doesn't return until it does. This is made clear in the documentation and
should be well-understood by anyone using Invoke().

In any case, the potential of such a misconception isn't a reason to use
BeginInvoke() instead. It's just a reason to use Invoke() properly.

I haven't seen much evidence that "Control.Invoke is widely misused and
misunderstood". If anything, I've seen plenty of evidence that what's
widely misunderstood is that one should in fact be using
Control.Invoke(). I think it's true that there are probably lots of
people who really don't understand exactly what Invoke() does, or why the
cross-thread issues are a problem, but that doesn't mean that Invoke() is
being misused.
I took your code and moved & replaced the synchronous call (with
async) ThreadPool.QueueUserWorkItem(_InitListView);
in Form_Load() and moved it to the main form's constructor; after the
InitializeComponent() method call; the difference was, I created a
small async class, implemented the Command pattern (i'e. Execute()),
and loaded 500 bitmap images(all the same 32x32). The result, no
deadlocks, no MDA's, no freezing, simply free to the end user.

So? My code didn't have a problem. It had no deadlocks, no MDAs, no
freezing, and no interruption in the user interface's responsiveness. So
you reimplemented it using some other technique that also doesn't have a
problem. What have you shown by that?

I also don't understand your statement "replaced the synchronous call
(with async) ThreadPool.QueueUserWorkItem(_InitListView); in
Form_Load()". The call to QueueUserWorkItem isn't a synchronous call: it
returns immediately, and the code in _InitListView runs asynchronously to
the main GUI thread. That's the whole point of the example.

Pete
 
P

Paul E Collins

Peter Duniho said:
Mine works fine on my computer with or without the call to Sleep().

I am using Visual Studio 2005 version 8.0.50727.42 with Framework
2.0.50727 on Windows XP 5.1 Build 2600.xpsp_sp2_gdr.070227-2254 : SP2.

Here is a minimal code sample that locks up the UI with an hourglass
and no repainting. If I add a Thread.Sleep(1); in the loop then it
doesn't lock up at all. Is this to be expected? Doesn't it happen for
you?


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;


namespace WindowsApplication1
{
public class Form1 : Form
{
private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.iml = new
System.Windows.Forms.ImageList(this.components);
this.SuspendLayout();
//
// iml
//
this.iml.ColorDepth =
System.Windows.Forms.ColorDepth.Depth8Bit;
this.iml.ImageSize = new System.Drawing.Size(16, 16);
this.iml.TransparentColor =
System.Drawing.Color.Transparent;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F,
13F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 274);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

}

#endregion

private System.Windows.Forms.ImageList iml;


public Form1()
{
InitializeComponent();
}


private void Form1_Load(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new
WaitCallback(CreateImages));
}


private void CreateImages(object dataFromOtherThread)
{
while (true) // or any reasonably large number of
iterations
{
Bitmap bmp = new Bitmap(32, 32);

this.Invoke(
(MethodInvoker) delegate { iml.Images.Add(bmp); }
);
}
}
}
}
 
P

Peter Duniho

I am using Visual Studio 2005 version 8.0.50727.42 with Framework
2.0.50727 on Windows XP 5.1 Build 2600.xpsp_sp2_gdr.070227-2254 : SP2.

Here is a minimal code sample that locks up the UI with an hourglass
and no repainting. If I add a Thread.Sleep(1); in the loop then it
doesn't lock up at all. Is this to be expected? Doesn't it happen for
you?

I guess that depends on what the exact behavior you're seeing is. I
suspect the behavior we're seeing is different, but I can see how
different people might interpret the same behavior differently. So with
that in mind...

First, there are some minor differences in our reported system
configuration. I'm running VS 2005 Pro, .NET 2.0, XP SP2 which is all
basically the same as you. However, my VS version number is actually
8.0.50727.762 (SP.050727-7600), and my OS version is "5.1 (Build
2600.xpsp_sp2_rtm.040803-2158 : Service Pack 2)" (in the System
Configuration utility, it's listed as "5.1.2600.2180
(xpsp_sp2_rtm.040803-2158)" (in the HAL section).

My PC is basically a Core 2 Duo, 2.33Ghz, 2GB of RAM. There's a twist
though, as I'm running XP in a virtual machine, with 768MB of RAM. For
most things though, since the VM is hardware-supported, performance is
identical as compared to running without the VM.

I don't see why those minor differences would have such a significant
effect, but given that there appears to be a difference, maybe they do.

Now, as for what happens when I run the code you posted: I do not find
that the application locks up, nor do I get an hourglass cursor and no
repainting.

You'll note that the code you posted endlessly creates new Bitmap
instances and never releases them, so this obviously eventually results in
an exception (oddly enough, instead of some sort of out-of-memory
exception, I get an "invalid parameter" in the Bitmap constructor...just
another GDI oddity I suppose, trying to tell me that if only I'd asked for
a smaller Bitmap it would've worked :) ). That exception takes about five
minutes to happen on my computer, after about 360000 iterations.

The other thing about the code is that it's in a fairly tight loop. But
every time it calls Invoke() it yields to the main GUI thread. There's no
way for it to starve the loop, but it does have the effect of slowing down
the responsiveness for painting. The Invoke() itself, which requires two
thread switches for each iteration of the loop, is certainly part of the
performance overhead, but I believe the main issue is probably because the
anonymous method instances pile up and eventually have to be garbage
collected, so when the GC steps in everything else has to stop
momentarily. This causes almost a 10X slowdown once the GC kicks in and
starts having to do things regularly (see below for specifics).

So, the fact that this is happening does mean that repainting slows down.
But at least on my computer, it doesn't stop altogether. The application
remains responsive, though occasionally it lags as much as half a second
to a second behind the user actions (dragging the window, obscuring it
with another, etc.)

Because this is timing related, it's possible that if your hardware is
significantly different from mine, that could explain a perceived
difference in responsiveness. But beyond that, the code you posted should
remain responsive on your computer, at least to some extent. If it
literally locks up completely, and you are unable to note _any_ repainting
or other UI response, that would be very odd.

IMHO, the next thing you should do is add a Debug.WriteLine() to the loop,
with a counter printed as part of the debug output. That will help you
differentiate between a true lock-up, in which case you'll only see the
output once (assuming you put it before the call to Invoke()), and just
some sort of sluggish behavior, in which case you'll see the counter
iteratively being printed out.

If the former, then you definitely have some kind of weird problem with
your installation; there's no reason that the code should deadlock, and it
doesn't on my computer.

If the latter, well...the main problem you're apparently running into is
trying to do too much given your configuration (whatever that is). I
don't know how many Bitmaps you're trying to make and add to a list, but I
suppose if it's enough of them you might have problems. But even in that
case, you should see the program _eventually_ respond.

For what it's worth, on my PC, it can run though about 7000 iterations of
the loop per second, at least initially (as the program continues,
iterations take longer because of the memory consumption and garbage
collection overhead, dropping to between 800 and 1000/sec).

Note that for any _reasonable_ number of Bitmaps (say, only a thousand or
so), at least on my computer the operation completes so quickly that any
interruption in responsiveness really isn't relevant. Depending on the
speed of your computer, you may find things work out differently. You
mentioned that batching up the Bitmaps helps performance, so perhaps
that's the solution (assuming you're making a much larger number of them
than I think is reasonable :) ). Most of the overhead in the loop is the
invoking itself, so if you can vastly reduce the number of times you have
to make that thread switch, you can greatly improve performance, returning
responsiveness to the application.

Pete
 
P

Paul E Collins

Peter Duniho said:
My PC is basically a Core 2 Duo, 2.33Ghz, 2GB of RAM.

This one has 2.8 GHz / 512 MB.
You'll note that the code you posted endlessly creates new Bitmap
instances and never releases them, so this obviously eventually
results in an exception

Hmm. I'd assumed that the Bitmap instances assigned to an ImageList
would be disposed with that ImageList when the parent form was closed.
OTOH, I don't know what the ImageList does internally and it might
make copies instead. Perhaps I'd better keep references to the
originals and do it myself.

I'll typically be creating only about 300 bitmaps in my program, but
that infinite loop made the problem clearer - rather than having it
stop sooner or later when the bitmaps were all done - and avoided
having to include a lot of longer code for how I'm actually painting.
(oddly enough, instead of some sort of out-of-memory exception,
I get an "invalid parameter" in the Bitmap constructor...just
another
GDI oddity I suppose, trying to tell me that if only I'd asked for
a smaller Bitmap it would've worked :) ).

A lot of GDI+ methods seem to give that "Invalid parameter" exception
instead of something more immediately obvious.
The other thing about the code is that it's in a fairly tight loop.
But every time it calls Invoke() it yields to the main GUI thread.
There's no way for it to starve the loop, but it does have the
effect of slowing down the responsiveness for painting.

Yes, that seems to be the case. I added code to my thread that would
repeatedly increment a number in the form's title bar, and (despite
the hourglass and lack of painting) I saw that number go up smoothly
and continuously. So the solution seems to be putting that Sleep in
there.
but I believe the main issue is probably because the anonymous
method
instances pile up and eventually have to be garbage collected,

I hadn't thought of that, but I expect it's a non-issue with my actual
300 or so bitmaps.

I'd quite like to cache the bitmap for each Tile instance in my
program, so I don't have to create them all at once for this ListView,
but it's complicated by the fact that the underlying logical palette
for all Tiles can change - e.g. the user might globally replace blue
with red. No doubt I'll manage to work something out, though. Thanks.

Eq.
 
P

Peter Duniho

Hmm. I'd assumed that the Bitmap instances assigned to an ImageList
would be disposed with that ImageList when the parent form was closed.

Well, yes. But the code you posted never closes the parent form.
[...]
The other thing about the code is that it's in a fairly tight loop.
But every time it calls Invoke() it yields to the main GUI thread.
There's no way for it to starve the loop, but it does have the
effect of slowing down the responsiveness for painting.

Yes, that seems to be the case. I added code to my thread that would
repeatedly increment a number in the form's title bar, and (despite
the hourglass and lack of painting) I saw that number go up smoothly
and continuously. So the solution seems to be putting that Sleep in
there.

I don't see how that's a good solution. Of course, since I can't
reproduce the behavior you're seeing it's hard for me to say. On my
computer, 300 iterations of the loop would take less than 1/30th of a
second. The user should never notice that.

Using Sleep(1) certainly would slow down the background thread, which
would alleviate any performance issues it might be causing. But for only
300 iterations on a 2.8Ghz PC, I just don't see why you should be having
performance issues in the first place, at least based on the example you
provided. And using Sleep(1) will greatly slow down the total time it
takes to initialize all 300 bitmaps. The improvement on the GUI thread
comes at a significant cost to the worker thread.

That said, since you've proved that this is _not_ a deadlock problem, I
think it's a good time now to revisit Greg's suggestion, which is to use
BeginInvoke() instead of Invoke(). Certainly one issue is that the two
threads are being tied together by the Invoke(). As I noted elsewhere, I
don't feel that using BeginInvoke() is a proper technique for specifically
avoiding deadlock. But it _is_ a useful technique for disconnecting two
threads when being tied to each other causes performance issues.

In this case, using BeginInvoke() would allow the Windows thread scheduler
to be able to do a better job allowing the main GUI thread remain
responsive for handling user interaction. This _might_ improve the
situation, depending on what's really going wrong.

Additionally, you will probably want to look at the AddRange() method you
mentioned earlier. Anything you can do to reduce the number of
cross-thread calls (whether synchronous or asynchronous) will help
performance. And you may even get a modest performance improvement just
with respect to using the ImageList itself.

Pete
 
G

Greg

I am using Visual Studio 2005 version 8.0.50727.42 with Framework
2.0.50727 on Windows XP 5.1 Build 2600.xpsp_sp2_gdr.070227-2254 : SP2.

Here is a minimal code sample that locks up the UI with an hourglass
and no repainting. If I add a Thread.Sleep(1); in the loop then it
doesn't lock up at all. Is this to be expected? Doesn't it happen for
you?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsApplication1
{
public class Form1 : Form
{
private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.iml = new
System.Windows.Forms.ImageList(this.components);
this.SuspendLayout();
//
// iml
//
this.iml.ColorDepth =
System.Windows.Forms.ColorDepth.Depth8Bit;
this.iml.ImageSize = new System.Drawing.Size(16, 16);
this.iml.TransparentColor =
System.Drawing.Color.Transparent;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F,
13F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 274);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

}

#endregion

private System.Windows.Forms.ImageList iml;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new
WaitCallback(CreateImages));
}

private void CreateImages(object dataFromOtherThread)
{
while (true) // or any reasonably large number of
iterations
{
Bitmap bmp = new Bitmap(32, 32);

this.Invoke(
(MethodInvoker) delegate { iml.Images.Add(bmp); }
);
}
}
}



}- Hide quoted text -

- Show quoted text -


Here is the code sample I referred to.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;


namespace WindowsApplication1
{

public delegate void LoadBitmapProgressDelegate(int sender);

public interface ICommand
{
void Execute();
}

public class Form1 : Form
{
private System.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code


/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.iml = new
System.Windows.Forms.ImageList(this.components);
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// iml
//
this.iml.ColorDepth =
System.Windows.Forms.ColorDepth.Depth8Bit;
this.iml.ImageSize = new System.Drawing.Size(16, 16);
this.iml.TransparentColor =
System.Drawing.Color.Transparent;
//
// button1
//
this.button1.Location = new System.Drawing.Point(52, 177);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(174, 32);
this.button1.TabIndex = 0;
this.button1.Text = "Too Fast? Click To See Again...";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new
System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F,
13F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 274);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}


#endregion

private Button button1;

private System.Windows.Forms.ImageList iml;

public Form1()
{
InitializeComponent();

AsyncBitmapLoader asyncLoader = new
AsyncBitmapLoader(this.iml, null);
asyncLoader.LoadBitmapProgress += new
LoadBitmapProgressDelegate(OnAsyncLoadBitmapProgress);
asyncLoader.Execute();
}

void OnAsyncLoadBitmapProgress(int sender)
{
MethodInvoker updateFormsTitle = delegate { this.Text =
"Bitmap # " + sender.ToString(); };
// if the main form is closed before the loop inside
CreateImages,
// an ObjectDisposedException will be thrown.
if (!this.Disposing)
{
if (this.InvokeRequired)
{ this.Invoke(updateFormsTitle); } else { updateFormsTitle(); }
}
}

private void button1_Click(object sender, EventArgs e)
{
AsyncBitmapLoader asyncLoader = new
AsyncBitmapLoader(this.iml, null);
asyncLoader.LoadBitmapProgress += new
LoadBitmapProgressDelegate(OnAsyncLoadBitmapProgress);
asyncLoader.Execute();
}
}

public class AsyncBitmapLoader : ICommand
{
private delegate void MyCustomDelegate(object
dataFromOtherThread);
private MyCustomDelegate m_invokeMe;
private ImageList m_imageList;
private object m_dataFromOtherThread;
public event LoadBitmapProgressDelegate LoadBitmapProgress;

private void OnLoadBitmapProgress(int sender)
{
if (LoadBitmapProgress != null)
LoadBitmapProgress(sender);
}

public AsyncBitmapLoader(ImageList imageList, object
dataFromOtherThread)
{
m_dataFromOtherThread = dataFromOtherThread;
m_invokeMe = new MyCustomDelegate(CreateImages);
m_imageList = imageList;
}

public void Execute()
{
m_invokeMe.BeginInvoke(m_dataFromOtherThread, CallBack,
null);
}

private void CallBack(IAsyncResult ar)
{
// get the dataset as output]
m_invokeMe.EndInvoke(ar);
}

private void CreateImages(object dataFromOtherThread)
{
for (int i = 1; i <= 500; i++)
{
Bitmap bmp = new Bitmap(32, 32);
m_imageList.Images.Add(bmp);
OnLoadBitmapProgress(i);
}
//while (true) // or any reasonably large number of
iterations
//{
// Bitmap bmp = new Bitmap(32, 32);


// this.Invoke(
// (MethodInvoker)delegate { iml.Images.Add(bmp); }
// );
//}
}
}

}


Greg
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top