|
Kenneth Haugland wrote: what I was worried about was that there was just "one function" in memory, so when it was used no other threads could use the function, thereby making the multithreading single thread instead? No, there's no such thing as automatic method-entry-queuing. That's why there are synchronization objects. If you don't use any your threads will execute whatever "comes their way".
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
If I read you correctly, each thread will have its own function to use?
|
|
|
|
|
Code (instructions) only exist(s) once in memory and that's not a problem because it (usually) never changes. But each thread has its own stack for local variable allocation. When thread #2 enters DoCalcualtions while thread #1 is still executing DoCalcualtions they will have individual "instances" of the local variable result .
Ref: memory management - What and where are the stack and heap? - Stack Overflow[^]
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
I have to display thumbnails in a ListView control but the problem is that the control is locked until it finally loads and displays all thumbnails. Is there a simple way to display each thumbnail immediately after its loaded (in the same way Windows Exporer displays thumbnails in real time)?
This is the code for loading ListView with thumbnails:
void LoadListView()
{
int i = 0;
listView1.LargeImageList = imageList1;
foreach (string file in fileList)
{
Bitmap thumbnail = MakeThumbnail(file, 200, 200);
imageList1.Images.Add(thumbnail);
ListViewItem caption = new ListViewItem(Path.GetFileName(file));
caption.ImageIndex = i;
listView1.Items.Add(caption);
i++;
}
}
And this is the code for creating thumbnails with the correct aspect ratio:
Bitmap MakeThumbnail(string filename, int thumbWidth, int thumbHeight)
{
Bitmap thumbnail = null;
try
{
Bitmap bmp = new Bitmap(filename);
ImageFormat loFormat = bmp.RawFormat;
decimal ratio;
int newImgWidth = 0;
int newImgHeight = 0;
if (bmp.Width < thumbWidth && bmp.Height < thumbHeight) return bmp;
if (bmp.Width > bmp.Height)
{
ratio = (decimal)thumbWidth / bmp.Width;
newImgWidth = thumbWidth;
decimal temp = bmp.Height * ratio;
newImgHeight = (int)temp;
}
else
{
ratio = (decimal)thumbHeight / bmp.Height;
newImgHeight = thumbHeight;
decimal temp = bmp.Width * ratio;
newImgWidth = (int)temp;
}
thumbnail = new Bitmap(thumbWidth, thumbHeight);
Graphics g = Graphics.FromImage(thumbnail);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.FillRectangle(Brushes.White, 0, 0, thumbWidth, thumbHeight);
g.DrawImage(bmp,
(thumbWidth - newImgWidth) / 2,
(thumbHeight - newImgHeight) / 2,
newImgWidth, newImgHeight);
bmp.Dispose();
}
catch
{
return null;
}
return thumbnail;
}
modified 5-Nov-17 19:25pm.
|
|
|
|
|
The ListView control is not locked. There's no such concept as "locking" a control.
The problem is you have the UI (startup) thread doing all of the work of generating the thumbnails. Since the thread is tied up doing that work it can't get back to processing the message pump and handling all the WM_PAINT messages that are stacking up.
The solution is to move the thumbnail generation to a background thread. Look into using a BackgroundWorker to do this.
|
|
|
|
|
My bad. I meant the form is locked: it literally freezes when there are many thumbnails to process. Whoever did this ListView design at Microsoft did a bad job if it doesn't work smoothly out of the box.
|
|
|
|
|
The Form is just another control and it's bound by the same limitations OF YOUR CODE. You're bogging down the UI thread with the thumbnail generation. It has NOTHING AT ALL to do with the design of the ListView control. ALL controls and the form will not respond to anything until your method that generates the thumbnails is done and returns.
You have no choice but to move the "long running code" to a background thread. This frees up the UI thread to do all of the painting and response to mouse moves, clicks, keyboard, ...
This is how Windows works. It has nothing to do with the controls, the language your using, the type of app you're writing, ...
|
|
|
|
|
I added a BackgroundWorker to my project, but it doesn't work at all.
I started the BackgroundWorker with:
backgroundWorker1.RunWorkerAsync();
Then I called the function LoadListView():
void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
LoadListView();
}
|
|
|
|
|
You can't touch a UI control from a background thread. The worker should ONLY generate the thumbnail and return the resulting image. The code on the UI thread should get that image back from the background worker and update the ListView with it.
|
|
|
|
|
"The worker should ONLY generate the thumbnail and return the resulting image."
But how to return the resulting image? Both BackgroundWorker1DoWork and RunWorkerAsync() return void.
What I want to achieve is this: generate a thumbnail, display it in ListView, generate another thumbnail, display it in ListView, and so on. The problem is that ImageList is linked to that ListView. How do I force the ListView to display the thumbnail instead of waiting for all thumbnails to be created?
modified 4-Nov-17 12:07pm.
|
|
|
|
|
Take a look here, if you haven't already: BackgroundWorker Class (System.ComponentModel)[^] And especially at the sample code at the bottom.
I'd suggest you start only one background worker which produces the thumbnails in a loop and after each one you use its method ReportProgress -- but not for reporting the progress-percentage like in the sample but using the other member of ProgressChangedEventArgs[^] (UserState ) to deliver the thumbnail bitmap to your UI thread. Which then does basically the same as in LoadListView but only for a single already generated thumbnail.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
"I'd suggest you start only one background worker which produces the thumbnails in a loop and after each one you use its method ReportProgress"
Like this?
void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (var i = 0; i < fileList.Count; i++)
{
Bitmap thumbnail = MakeThumbnail(fileList[i], 200, 200);
backgroundWorker1.ReportProgress(i);
}
}
"but not for reporting the progress-percentage like in the sample but using the other member of ProgressChangedEventArgs[^] (UserState) to deliver the thumbnail bitmap to your UI thread."
This is unclear to me. How do I actually use UserState to deliver the thumbnail? That MSDN example is very simplistic (it just shows UserState). This is advanced stuff for me.
|
|
|
|
|
Look at the listed members of the class
There's this one: BackgroundWorker.ReportProgress Method (Int32, Object) (System.ComponentModel)[^]
As the first argument you could pass i or calculate the progress in percent (or pass 0 if you don't care) and for the second argument you pass thumbnail .
In your ProgressChanged -eventhandler-method then you'll need to cast ProgressChangedEventArgs.UserState to Bitmap .
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
OK. So:
backgroundWorker1.ReportProgress(i, thumbnail);
Now the difficult part:
void BackgroundWorker1ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
imageList1.Images.Add((Bitmap)e.UserState);
}
Of course it doesn't work. How do I make the ImageList/ListView to accept this new thumbnail?
|
|
|
|
|
In your original LoadListView() you did more stuff than just adding the Bitmap to the ImageList
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Yes. I added also a caption for each item. I wanted to fix that later. Anyway, I modified the code a little and it works, but unfortunately the ListView flickers a lot (it's not useable like that):
void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (var i = 0; i < fileList.Count; i++)
{
Bitmap thumbnail = MakeThumbnail(fileList[i], 200, 200);
backgroundWorker1.ReportProgress(i, thumbnail);
}
}
void BackgroundWorker1ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
imageList1.Images.Add((Bitmap)e.UserState);
ListViewItem caption = new ListViewItem(Path.GetFileName(fileList[e.ProgressPercentage]));
caption.ImageIndex = e.ProgressPercentage;
listView1.Items.Add(caption);
}
void LoadListView()
{
listView1.LargeImageList = imageList1;
backgroundWorker1.RunWorkerAsync();
}
Any idea how to improve it?
modified 4-Nov-17 13:48pm.
|
|
|
|
|
The actual entry in a ListView is a ListViewItem that has been added to its Items collection. Just adding a Bitmap to its ImageList doesn't show an entry. The ImageList is just a "repository" of images that can be utilized by the ListViewItems - which is handy if you want to associate the same image with multiple ListViewItems.
edit: Posted before I saw your edited message with code.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
I updated the previous post. Please read it again.
|
|
|
|
|
Take a look here: ListView.BeginUpdate Method (System.Windows.Forms)[^]
You could call that before you start the background worker and call EndUpdate when the background worker is done (another event which you haven't utilized yet I assume).
If you want to see the items being listed while the background worker is still working you would have to "batch" the Bitmaps (in a List<bitmap> or an array for example) before calling ReportProgress and then pass that List/array/batch for UserState. Then use BeginUpdate at the start of BackgroundWorker1ProgressChanged and EndUpdate at the end. And then play around with the batch size to find a value you're comfortable with.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
I declared a global const int batchSize = 10 and a global variable counter .
BackgroundWorker1DoWork sends a new batch of thumbnails every batchSize times or if the i counter already reached the limit (fileList.Count - 1 ):
void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
List<Bitmap> thumbnailList = new List<Bitmap>();
for (var i = 0; i < fileList.Count; i++)
{
Bitmap thumbnail = MakeThumbnail(fileList[i], 200, 200);
thumbnailList.Add(thumbnail);
if (((thumbnailList.Count == batchSize) && (batchSize < fileList.Count)) ||
(i == (fileList.Count - 1)))
{
backgroundWorker1.ReportProgress(i, thumbnailList);
thumbnailList.Clear();
}
}
}
And the second function:
void BackgroundWorker1ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
List<Bitmap> list = (List<Bitmap>)e.UserState;
listView1.BeginUpdate();
if (list.Count > 0)
{
foreach (Bitmap tn in list)
{
imageList1.Images.Add(tn);
ListViewItem caption = new ListViewItem(Path.GetFileName(fileList[counter]));
caption.ImageIndex = counter;
listView1.Items.Add(caption);
counter++;
}
}
listView1.EndUpdate();
}
Unfortunatelly it doesn't work. thumbnailList.Count from the second function reports 0, so apparently thumbnailList.AddRange((List<Bitmap>)e.UserState); is one of the problems. Any ideas?
PS: I uploaded the test project here: TinyUpload.com[^] If you want to test it, drag and drop pictures over the form.
modified 4-Nov-17 16:37pm.
|
|
|
|
|
List<Bitmap> thumbnailList = new List<Bitmap>();
backgroundWorker1.ReportProgress(i, thumbnailList);
Bitmap thumbnail = (Bitmap)e.UserState;
..... = (notbitmapbutsomethingelse)e.UserState;
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
I updated the previous post. The app still doesn't work.
|
|
|
|
|
When you pass the list via ProgressChangedEventArgs you pass a reference to the (currently) one and only existing instance of it which you immediately Clear() afterwards (I missed that previously). Instead of clearing it, create a new List<Bitmap> for the next batch.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Thanks. Unfortunately sometimes I get this error after I drag and drop some files:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.ThrowHelper.ThrowArgumentOutOfRangeException()
at System.Collections.Generic.List`1.get_Item(Int32 index)
at test1.MainForm.BackgroundWorker1ProgressChanged(Object sender, ProgressChangedEventArgs e)
at System.ComponentModel.BackgroundWorker.OnProgressChanged(ProgressChangedEventArgs e)
at System.ComponentModel.BackgroundWorker.ProgressReporter(Object arg)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.UnsafeInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at System.Delegate.DynamicInvoke(Object[] args)
at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at test1.Program.Main(String[] args)
Any idea how to fix this bug? I think it's related to those Bitmap lists.
By the way, this is the current code:
void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
List<Bitmap> thumbnailList = new List<Bitmap>();
for (var i = 0; i < fileList.Count; i++)
{
Bitmap thumbnail = MakeThumbnail(fileList[i], thumbnailSize, thumbnailSize);
thumbnailList.Add(thumbnail);
if (((thumbnailList.Count == batchSize) && (batchSize < fileList.Count)) ||
(i == (fileList.Count - 1)))
{
backgroundWorker1.ReportProgress(i, thumbnailList);
thumbnailList = new List<Bitmap>();
}
}
}
void BackgroundWorker1ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
List<Bitmap> thumbnailList = (List<Bitmap>)e.UserState;
listView1.BeginUpdate();
if (thumbnailList.Count > 0)
{
foreach (Bitmap thumbnail in thumbnailList)
{
imageList1.Images.Add(thumbnail);
ListViewItem caption = new ListViewItem(Path.GetFileName(fileList[counter]));
caption.ImageIndex = counter;
listView1.Items.Add(caption);
counter++;
}
}
listView1.EndUpdate();
}
|
|
|
|
|
Two minor things first:
This:
&& (batchSize < fileList.Count) ..isn't neccessary because it's covered already by this:
|| (i == (fileList.Count - 1)
And this:
if (thumbnailList.Count > 0) ..isn't neccessary because if the list is empty then the foreach-loop will just do nothing.
Now, to the problem at hand - these lines of the exception stack trace:
at System.Collections.Generic.List`1.get_Item(Int32 index)
at test1.MainForm.BackgroundWorker1ProgressChanged(Object sender, ProgressChangedEventArgs e) ..tell you that the problem occurs when trying to access a List<> with its indexer in BackgroundWorker1ProgressChanged . The only candidate for this is:
fileList[counter] So it seems somehow your counter gets out of sync with the "actual progress". No idea why; maybe it's in the code you haven't shown here.
But that's a point I planned to suggest to you to improve anyway: Change it so that BackgroundWorker1ProgressChanged doesn't need to assume the index of the "delivered" bitmap in fileList by delivering not only the bitmaps but the bitmaps with their index. Either create a class for that (with Bitmap and Index as properties) and store instances of that class in the list (instead of just the Bitmap) or use a Tuple<Bitmap, int> instead of the custom class. Or, instead of using the index, use the filename and ListViewItem.ImageKey[^] instead of ImageIndex . That way you wouldn't need fileList in BackgroundWorker1ProgressChanged at all.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|