By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,144 Members | 872 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,144 IT Pros & Developers. It's quick & easy.

Populate an ImageList from another thread

P: n/a
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); }
);
}
}
Dec 15 '07 #1
Share this Question
Share on Google+
10 Replies


P: n/a
On Fri, 14 Dec 2007 17:05:54 -0800, Paul E Collins
<fi******************@CL4.orgwrote:
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(fals e);
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.Win dows.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;
}
}
Dec 15 '07 #2

P: n/a
"Peter Duniho" <Np*********@nnowslpianmk.comwrote:
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.
Dec 15 '07 #3

P: n/a
On Fri, 14 Dec 2007 18:27:21 -0800, Paul E Collins
<fi******************@CL4.orgwrote:
"Peter Duniho" <Np*********@nnowslpianmk.comwrote:
>Well, you could do it correctly. That would work.

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
Dec 15 '07 #4

P: n/a
On Dec 14, 5:05 pm, "Paul E Collins" <find_my_real_addr...@CL4.org>
wrote:
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
Dec 15 '07 #5

P: n/a
On Fri, 14 Dec 2007 21:10:26 -0800, Greg Cadmes <gc*****@gmail.comwrote:
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
Dec 15 '07 #6

P: n/a
On Dec 14, 10:58 pm, "Peter Duniho" <NpOeStPe...@nnowslpianmk.com>
wrote:
On Fri, 14 Dec 2007 21:10:26 -0800, Greg Cadmes <gcad...@gmail.comwrote:
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
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...nvocation.aspx

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

Greg
Dec 15 '07 #7

P: n/a
On Sat, 15 Dec 2007 11:12:15 -0800, Greg Cadmes <gc*****@gmail.comwrote:
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...nvocation.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
Dec 15 '07 #8

P: n/a
On Dec 15, 11:40 am, "Peter Duniho" <NpOeStPe...@nnowslpianmk.com>
wrote:
On Sat, 15 Dec 2007 11:12:15 -0800, Greg Cadmes <gcad...@gmail.comwrote:
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...nvocation.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


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
Dec 15 '07 #9

P: n/a
"Peter Duniho" <Np*********@nnowslpianmk.comwrote:
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); }
);
}
}
}
}
Dec 17 '07 #10

P: n/a
On Sun, 16 Dec 2007 17:20:27 -0800, Paul E Collins
<fi******************@CL4.orgwrote:
"Peter Duniho" <Np*********@nnowslpianmk.comwrote:
>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?
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
Dec 17 '07 #11

This discussion thread is closed

Replies have been disabled for this discussion.