views:

428

answers:

4

Our app's master/details view uses a datagridview as the master, and a custom control as the details view. The details view takes a long time to compute and render, making cursoring up/down the master view painfully slow.

Therefore, we'd like the details view to run asynchronously (in a separate UI thread) with change notifications from the master.

Creating a form in a separate thread is relatively straightforward, as Application.Run takes a form parameter.

Is there a way to run a winforms control on a separate thread? I'm aware that native windows in different threads can have a parent/child relationship, just not sure how to set that up using winforms.

TIA,

+1  A: 

If you are firing off the update of the detail view in code, you can greatly improve the usability by sleeping 500ms between the time that the user selects the master record, and the time you update the detail view.

This gives the user 1/2 second to scroll to the next record without the details view updating at all.

Robert Harvey
We actually do have such a delay (and should probably have mentioned it in the original question, thanks.) However, when the details view does get around to updating it blocks the user's scrolling noticeably, which is why we need to do it in a separate thread. Is there a way to do this?
bright
See my other answer for the threading. If the user scrolls during the delay period you need to restart the timer.
Robert Harvey
+2  A: 

Updating the UI from a Secondary Thread
http://msdn.microsoft.com/en-us/magazine/cc188732.aspx

Intuitively, you also ought to be able to accomplish the same thing by using a BackgroundWorker. The BackgroundWorker is designed to update UI things like progress bars while executing stuff in the background, and it can be cancelled during its operation.

Robert Harvey
Unfortunately, no. The actual UI needs to be in a different thread because the compute and rendering call in to different Winforms controls, which can only be called on their UI thread (this is a Winforms restriction.) To call them safely we'd need to perform InvokeSync back to the main thread, which would again mean blocking the ui.Assuming we've gone as far as we can go with a single UI thread, the original question remains: is there a way to set up a winforms control on a different UI thread?
bright
+2  A: 

Is the slowdown caused by the loading of the data, or the population of the UI itself?

Most of the time it's the former, so if that's the case, then the logic that does the data loading should be abstracted into a different thread. The UI code can live in the main thread since updates are quick. You could use either a Thread or a BackgroundWorker in this situation. The key is to separate your data loading from your GUI population.

Jon Seigel
+1 Good point..
Robert Harvey
In our case it is very definitely updating and recomputing the view. The data is already in memory. It is the layout and drawing algorithms that are CPU intensive.
bright
Also (as mentioned in another comment below) these algorithms require calling in to child winforms controls, which can only be called on their UI thread.
bright
Layout and drawing are separate things, which could potentially be separated. If you could provide more specifics on what is going on in the view (please edit your original question), we may be able to give you more specific help with the problem.
Jon Seigel
Rather than pursue a totally different course, would like to steer this back to the original question - can a winforms control be run on a separate ui thread? We appreciate everyone's suggestions, but so far none has directly addressed the question, which is application independent and is purely about the framework.
bright
It's a good question, but I think there is a high chance that the answer you're looking for here is merely going to circumvent the real problem.
Jon Seigel
Why? There isn't anything sacrosonct about having a single UI thread. Each process gets one, for example. So does every app domain in a managed process. This is merely another technique of decoupling components, and is arguably cleaner than having all controls joined at the hip to their parent, even when they don't need to be. It seems likely that high performance controls like video streamers do this to avoid subjecting themselves to timing interactions with their hosts (such as browsers.) The reasons for the single UI thread are merely history and convenience.
bright
+1  A: 

If you're taking a speed hit during rendering, you should consider suspending layout until the form has completed updating, and then refresh the visible display once at the end.

this.SuspendLayout();

// Do control stuff here

this.ResumeLayout();

If that doesn't help, try this:

[DllImport("user32.dll")]

public static extern bool LockWindowUpdate(IntPtr hWndLock);
//
LockWindowUpdate(this.Handle);

// Do control stuff here

this.Refresh(); //Forces a synchronous redraw of all controls

LockWindowUpdate(IntPtr.Zero);

http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/8a5e5188-2985-4baf-9a0e-b72064ce5aeb

Robert Harvey
We've done that :)To give you a sense of the scope of this, we've been working on getting maximal perf in this area for several months. So yes, we've use a delayed update, backgroundworker, keep all the data in memory, and SuspendLayout(). We memoize as much of the computation as possible. We intersect the redraw area with the current graphics context.We've reached the point where we really feel the need to use a separate thread for our control. So again, is there a way to do this?
bright
I haven't found one yet, but I'll keep looking. The problem is that the message pump Winforms uses is essentially a Single-Threaded Apartment (STA) inherited from the Windows event model. Even in WPF (where some multi-threaded screen updates are supported), the message pump is still an STA. I think the only thing that might be left is to find a way to simply ABORT the screen update somehow if the user moves to a different record (since that user action essentially invalidates any screen update that may be in the process of completing).
Robert Harvey
OK here's another bit of information: http://stackoverflow.com/questions/1566791/run-multiple-ui-threads. It describes how to run two message pumps at the same time. Maybe that will help you somehow. I can't guarantee that it won't kill a kitten if you try it, though.
Robert Harvey
Thanks, however we're aware of that - the original question above mentions that it is straightfoward to run a second *form* in a separate UI thread. We're looking for a way to run a *control* in a separate UI thread. Application.Run takes a Form parameter, not a Control.
bright