views:

234

answers:

4

I have a PropertyGrid in my application that is used for editing arbitrary objects. I need to be able to run an arbitrary subroutine on another thread that also looks at these objects (search functionality, if you're curious). The obvious issue is that a user could be editing one of these objects at the same time my search thread is reading it, which would be preferable to avoid (although it probably won't result in anything critical since my search thread is just reading, not writing).

Calling lock(obj) is easy enough from my search thread, but after looking through the documentation and a brief skim through the PropertyDescriptorGridEntry code in Reflector, I cannot seem to find an analogous spot to use a System.Threading.Monitor.Enter()/Exit() call on the object in question on the PropertyGrid. I was hoping there would be BeginEdit and EndEdit events which would make this simple enough, but I can't seem to find any such thing. I'd rather not lock the entire object while it's on display in the PropertyGrid as that would obviously block my search thread until another object was selected.

I'm a bit new to the threading model of Windows Forms, so I'm hoping there's some obvious answer I've just overlooked. Any help?

Edit: Synchronously cloning my objects before running the search asynchronously will likely be inefficient enough that I might as well run the search itself synchronously - the point of running asynchronously is of course to allow my users to continue to work while the search is executing. The search needs to scale well, as the data set I am going through will eventually end up being arbitrarily large, which makes synchronous cloning look like it will cause the usability problem I am trying to avoid.

A: 

That will indeed not be thread safe. You could marshal bits of the searching code to the UI thread, but that will slow things down, and perhaps defeat the point of the threading...

How fast does the search need to be? Can you work against a clone? etc

Marc Gravell
I knew I shouldn't have gone into detail ;)
Jon Skeet
Edited question - cloning synchronously would likely defeat the purpose of the threading :(
Not Sure
A: 

Can you clone the data before displaying it, and get your search thread to work on the clone? If you want the search thread to "see" changes, you could respond to events on the PropertyGrid and perhaps make controlled changes to the clone somehow. (It's probably easier to just use the "stale" data though.)

I know cloning data sounds dreadfully inefficient, but threading certainly simpler when each thread works on completely independent data (or all the threads only read).

Jon Skeet
Edited question - cloning synchronously would likely defeat the purpose of the threading :(
Not Sure
A: 

I think you are going to have to do quite a bit of work for this.

First, you would have to create an implementation of ICustomTypeDescriptor which would base its implementation on whatever TypeDescriptor you would normally get for that type.

Then, in the implementations of the methods which expose the descriptors of the members you want to lock on, you would provide subclasses which derive from those descriptors and override the appropriate methods to wrap a lock around.

So for a property, you would implement GetProperties to return your specific subclasses of PropertyDescriptor. These subclasses would override the GetValue and SetValue methods (and others) and use a lock when accessing those variables.

It should be mentioned that locking like this in the UI thread is probably a bad idea, since you don't want to arbitrarily block operations on that thread. It might be better to just create a clone of the object and then have a method which updates the store of objects when you are done.

casperOne
Actually, in the case of PropertyGrid, you could do it more simply by subclassing PropertyTab and overriding GetProperties. Still a **lot** of work, but you don't actually need to shim via ICustomTypeDescriptor (nor TypeDescriptionProvider)
Marc Gravell
@Marc Gravell: You still have to create subclasses that derive from PropertyDescriptor and do what I indicated with overriding GetValue and SetValue. It's about the same amount of work. However, there is more general use for the ICustomTypeDescriptor implementation, which is why I'd go with that.
casperOne
But with ICustomTypeDescriptor, you need to control the type being displayed, rather than just the grid.
Marc Gravell
@Marc Gravell: You don't have much control over the grid when you derive from PropertyTab. Deriving from PropertyTab, you have to return the PropertyDescriptors which is just metadata, not values. You still have to lock access in the same place as when you implement ICustomTypeDescriptor.
casperOne
@Marc Gravell: And implementing ICustomTypeDescriptor is not that hard either in this case, you just have to defer to TypeDescriptor. The "hard" part is the subclass of PropertyDescriptor.
casperOne
*sigh* sadly enough this actually does seem like the most sensible solution.
Not Sure
A: 

I could be wrong, but it seems to me that you're coming from the wrong end.

There are two distinct issues you need to address.

Firstly, making sure that the PropertyGrid is only ever accessed on the UI thread. If any of its methods (including property getter/setters) are accessed from other threads, you'll suffer pain in mysterious ways. The exceptions are InvokeRequired() and Invoke, of course.

Secondly, making sure your search thread can run properly.

To solve the first problem, either make sure your objects are never modified except by the UI thread, or make all of your event triggers thread aware so that your objects events (such as PropertyChanged) are only ever triggered on the UI thread.

The second problem is easier - as long as your search thread only READS from your core objects, everything should work fine. Yes, your search thread might inadvertently see some partially updated data, but is that really an issue?

A couple of final thoughts ...

  • Don't implement your search to iterate through the PropertyGrid itself; obtain a list of the objects up front (they don't need to be clones) and work through that instead.

  • Have you considered using Idle Processing to do the search? The Application object fires an event every time the application runs out of messages to process - you could hook into this and perform 1 step of your search each time the event is fired. Kind of a poor-mans threading, but with none of the mutex/lock/semaphone headaches. I've used this to very good effect in the past.

Update: If I recall correctly, PropertyGrid respects the IEditableObject interface, calling BeginEdit as soon as you start modifying a row, and EndEdit when you move to a different row. You could leverage this to prevent your search thread from seeing incomplete changes.

Update 2: Following up, I've discovered what you already knew - that PropertyGrid doesn't respect the IEditableObject interface.

I do have another suggestion - though it might be more work than you want to invest.

Around the time when the System.Transactions namespace was introduced in .NET 2.0, I saw an article on using the ambient transaction to provide thread-isolation for objects. The idea is that your object properties have dual storage. First you have the committed value, visible to all threads, and you have a thread-local variable used to store uncommitted values on a per-thread basis. When a property is modified, the object enlists in any ambient transaction by storing the new value in the thread local. When the transaction commits or rolls back, the value for the thread is either stored or discarded.

Unfortunately, I can't find the original article, though it seems that CSLA provides this support. Hope this helps.

Bevan
"your search thread might inadvertently see some partially updated data" is the *only* issue that I'm concerned with and would prefer to avoid; nothing else is an issue (I do know what I'm doing...mostly). Idle Processing might have worked if my app wasn't so heavy-duty.
Not Sure
I do not believe you are recalling correctly - both your links and my memory indicate that IEditableObject with BeginEdit and EndEdit are used by DataGridView, not PropertyGrid.
Not Sure