views:

239

answers:

5

I want to paralelize a 3D voxel editor built on top of Windows Forms, it uses a raycaster to render so dividing the screen and getting each thread on a pool to render a part of it should be trivial.

The problem arises in that Windows Forms' thread must run as STA - I can get other threads to start and do the work but blocking the main thread while waiting for them to finish causes strange random deadlocks as expected.

Keeping the main thread unblocked would also be a problem - if, for example, the user uses a floodfill tool the input would be processed during the rendering process which would cause "in-between" images (an object partially colored, for example). Copying the entire image before every frame isn't doable either because the volumes are big enough to offset any performance gain if it has to be copied every frame.

I want to know if there is any workaround to get the amin thread to appear blocked to the user in a way that it will not be actually blocked but will delay the processing of input till the next frame.

If it isn't possible, is there a better design for dealing with this?

EDIT: Reading the anwsers I think I wasn't clear that the raycaster runs in real time, so showing progress dialogs won't work at all. Unfortunately the FPS is low enough (5-40 depending on various factors) for the input between frames to produce unwanted results.

I have already tried to implement it blocking the UI thread and using some threads of a ThreadPool to process and it works fine except for this problem with STA.

A: 

Show a modal "Please Wait" dialog using ShowDialog, then close it once your rendering is finished.

This will prevent the user from interacting with the form while still allowing you to Invoke to the UI thread (which is presumably your problem).

SLaks
Actually it's ShowDialog, not ShowModal ;)
Thomas Levesque
Fixed it; thanks.
SLaks
Doesn't ShowDialog stop all processing of the parent form until the child form is closed?
Eclipsed4utoo
That's exactly the point.
SLaks
Unfortunately it is a real time renderer so I can't do this.
John Doe
+6  A: 

This is a common problem. With windows forms you can have only one UI thread. Don't run your algorithm on the UI thread because then the UI will appear frozen.

I recommend running your algorithm and waiting for it to finish before updating the UI. A class called BackgroundWorker comes pre-built to do just this very thing.

Edit:

Another fact about the UI thread is that it handles all of the mouse and keyboard events, along with system messages that are sent to the window. (Winforms is really just Win32 surrounded by a nice API.) You cannot have a stable application if the UI thread is saturated.

On the other hand, if you start several other threads and try to draw directly on the screen with them, you may have two problems:

  1. You're not supposed to draw on the UI with any thread but the UI thread. Windows controls are not thread safe.
  2. If you have a lot of threads, context switching between them may kill your performance.

Note that you (and I) shouldn't claim a performance problem until it has been measured. You could try drawing a frame in memory and swapping it in at an appropriate time. Its called double-buffering and is very common in Win32 drawing code to avoid screen flicker.

I honestly don't know if this is feasible with your target frame rate, or if you should consider a more graphics-centered library like OpenGL.

Matt Brunell
I think I wasn't very clear, the intention is for the UI to appear frozen until the frame is drawn (5-40 FPS, depending on various parameters). If it keeps updating then the image may be partially changed between frames and it will look strange.
John Doe
A: 

Copying the entire image before every frame isn't doable either because the volumes are big enough to offset any performance gain if it has to be copied every frame.

Then don't copy the off-screen buffer on every frame.

pmf
A: 

If you don't want all the features offered by the BackgroundWorker you can simply use the ThreadPool.QueueUserWorkItem to add something to the thread pool and use a background thread. It would be easy to show some kind of progress while the background thread was performing it's operations as you can provide a delegate callback to notify you whenever a particular background thread is done. Take a look at ThreadPool.QueueUserWorkItem Method (WaitCallback, Object) to see what I'm referring you to. If you need something more complex you could always use the APM async method to perform your operations as well.

Either way I hope this helps.

EDIT:

  1. Notify user somehow that changes are being made to the UI.
  2. On a(many) background threads using the ThreadPool perform the ops you need to perform to the UI.
  3. For each operation keep a reference to the state for the operation so that you know when it completed in the WaitCallback. Maybe put them in some type of hash / collection to keep ref to them.
  4. Whenever an operation completes remove it from the collection that contains a ref to the ops that were performed.
  5. Once all operations have completed (hash / collection) has no more references in it render the UI with the changes applied. Or possibly incrementally update the UI

I'm thinking that if you are making so many updates to the UI while you are performing your operations that is what is causing your problems. That's also why I recommended the use of SuspendLayout, PerformLayout as you may have been performing so many updates to the UI the main thread was getting overwhelmed.

I am no expert on threading though, just trying to think it through myself. Hope this helps.

Wil P
I have already tried to use ThreadPool but I can't keep the loop running and show a progress bar because it is a real time renderer (not fast enough to avoid artifacts caused by the image partially changing between frames). If I block the UI thread while waiting for the rendering to finish the deadlocks happen randomly.
John Doe
You could use the APM to report progress for each item. I think you'd probably run into a reporting a meaningful deterministic progress though. I don't think you want to perform any blocking on UI thread to accomplish this. Are you performing a SuspendLayout and ResumeLayout on the respective controls when you're updating the UI to reflect the changes? To me it seems like you would want to SuspendLayout completely until you are done making your changes, but I am not certain I completly understand your problem.
Wil P
+1  A: 

Am I missing something or can you just set your render control (and any other controls that generate input events) to disabled while you're rendering a frame? That will prevent unwanted inputs.

If you still want to accept events while you're rendering but don't want to apply them until the next frame, you should leave your controls enabled and post the detail of the event to an input queue. That queue should then be processed at the start of every frame.

This has the affect that the user can still click buttons and interact with the UI (the GUI thread does not block) and those events are not visible to the renderer until the start of the next frame. At 5 FPS, the user should see their events are processed within 400ms worst case (2 frames), which isn't quite fast enough, but better than threading deadlocks.

Perhaps something like this:

Public InputQueue<InputEvent> = new Queue<InputEvent>();

// An input event handler.
private void btnDoSomething_Click(object sender, EventArgs e)
{ 
    lock(InputQueue)
    {
         InputQueue.Enqueue(new DoSomethingInputEvent());
    }
}

// Your render method (executing in a background thread). 
private void RenderNextFrame()
{
    Queue<InputEvent> inputEvents = new Queue<InputEvent>();

    lock(InputQueue)
    {
         inputEvents.Enqueue(InputQueue.Dequeue());
    }

    // Process your input events from the local inputEvents queue.
    ....

    // Now do your render based on those events.
    ....
}

Oh, and do your rendering on a background thread. Your UI thread is precious, it should only do the most trivial work. Matt Brundell's suggestion of BackgroundWorker has lots of merit. If it doesn't do what you want, the ThreadPool is also useful (and simpler). More powerful (and complex) alternatives are the CCR or the Task Parallel Library.

This seems like a good idea, I will try it and let you know if it works.
John Doe
It worked, thanks a lot!
John Doe