Resolved System.Argument.Exception when retrieving thumbnail.

MPIon

Well-known member
Joined
Jun 13, 2020
Messages
73
Location
England
Programming Experience
10+
This is a long shot, but might as well post in case someone has an answer to this.

I have 25 picture boxes on my form that are populated with thumbnails of images. I can scroll up and down using the mouse wheel, which repopulates the 25 boxes with a new set of images.
This normally works ok, but very occasionally, I get a System.Argument.Exception on one of the images. This seems to only happen if I scroll up and down quite fast.
It does not seem to be related to the file itself, as rerunning the program will show the thumbnail correctly.
The code to get the thumbnail is :-
C#:
boxData[boxNo].pictureBox.Image = shellFile.Thumbnail.LargeBitmap;
The error is :-
1692290713440.png

Putting a try block around the code does not help as it refuses to catch the error.

This is the stack trace if of any use :-
C#:
System.ArgumentException
  HResult=0x80070057
  Message=Parameter is not valid.
  Source=System.Drawing.Common
  StackTrace:
   at System.Drawing.Image.get_Width()
   at System.Drawing.Image.get_Size()
   at System.Windows.Forms.PictureBox.ImageRectangleFromSizeMode(PictureBoxSizeMode mode)
   at System.Windows.Forms.PictureBox.OnPaint(PaintEventArgs pe)
   at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
   at System.Windows.Forms.Control.WmPaint(Message& m)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam)

I am wondering if it is a timing issue in extracting the thumbnail from the cache?
 
If it happens when you scroll quickly then presumably it starts loading a second set of images before the first set has loaded properly. The solution is probably to ensure that doesn't happen and there may be multiple options for that. I would liken this to a situation I address often.

People often have a DataGridView bound to a BindingSource bound to a DataTable that they want to filter using a TextBox. The temptation is to handle the TextChanged event of the TextBox and do the filtrering there. The problem is that, if the user types multiple characters quickly, that approach will filter the data after every one. All but the last one are pointless and the others just slow the whole operation down.

What I suggest is to handle the TextChanged event and start/restart a Timer there, then do the filtering on the Tick of that Timer. The Interval is set such that the user will not cotice any significant delay between entering the text and the filtering being done but it will also not filter multiple times if they enter multiple characters with less than that interval between them.

I would expect that something similar should improve your situation, if not eradicate the issue altogether. When the user scrolls, you can call Stop and Start on your Timer. That means that, if the user scrolls multiple times quickly, the Timer will be restarted each time and only the last one will actually cause the images to change. The work that you're currently doing on a scroll, you do on the Tick of the Timer instead.

I would suggest starting with an Interval of 500 milliseconds and see how that performs. You can increase or decrease as required to get the best balance.
 
I'm just going off memory, but I vaguely recall the OP's code was using some variables being declared as static in his other threads. If the paint code was accessing those variables and/or data structures attached to those variables, I wouldn't at all be surprised if a race condition was happening. Even if the variables were not declared static, but the update code and the paint code didn't know how to ensure that they were not trampling on each other, I could also see the possibility of getting those kinds of transient errors.
 
If it happens when you scroll quickly then presumably it starts loading a second set of images before the first set has loaded properly. The solution is probably to ensure that doesn't happen and there may be multiple options for that. I would liken this to a situation I address often.

Thanks for the reply. That was my thought, and I do have a timer with an interval of 200ms. Its the timer that instigates the redraw, so with multiple scrolling, only the last scroll in the interval is acted upon (it keeps a count of how many scrolls have occurred, so it knows which images to draw). Maybe I will try increasing the interval to 500ms as you suggest.
I also had a sleep statement of 200ms after the timer event handler, which I thought would be sufficient time to ensure the thumbnails were redrawn before doing another one. Obviously that is not long enough.
I am assuming this problem occurs because after the code issues the shellFile.Thumbnail.LargeBitmap statement, the draws happens asynchronously, so it is not waiting for the draw to complete?

p.s. I have been working on the assumption that when the timer calls the event handler, if the handler is still running, it waits before kicking it off again. So there can't be two versions of the timer event handler running at the same time?
 
Last edited:
I have been working on the assumption that when the timer calls the event handler, if the handler is still running, it waits before kicking it off again. So there can't be two versions of the timer event handler running at the same time?

If you're using a WinForms Timer that raises its Tick event on the UI thread, that is correct. Your stack trace indicates a callback is invoked so something is happening on a secondary thread.
 
I would suggest starting with an Interval of 500 milliseconds and see how that performs. You can increase or decrease as required to get the best balance.

Thanks, I have increased my timer interval and that seems to have done the trick. The error has not happened again with a lot of testing. I will monitor and increase the time again if need be.
I did wonder though if there was some sort of code that I could put at the end of the timer to instruct it to wait for all drawing to complete?
 
You could use a critical section or a lock to ensure only one operation is happening at a time, then you now shift over the problem into potential deadlock territory if you don't apply and remove the critical sections in the correct order.
 
You could use a critical section or a lock to ensure only one operation is happening at a time, then you now shift over the problem into potential deadlock territory if you don't apply and remove the critical sections in the correct order.

Thanks, although since only one event in the Main thread can happen at once I don't think it would help. I can't put a lock in the drawing routines which seem to be happening asynchronously outside of my control. The timer solution seems to have solved the problem, as it has not happened again.
 
Until you run on a slow machine, or a machine that is bogged down doing a lot of work...
 

Latest posts

Back
Top Bottom