Waiting for a long process and still updating UI
Asked Answered
H

5

3

I've been attempting to create a task that writes to a database without blocking the UI thread. The biggest problem I'm having is waiting for that process to finish without the blocking happening.

I've been trying to avoid using DoEvents (though it's used quite frequently through this program right now, I'd like to move out of using it while moving forward).

I've attempted to create the process to run on a 2nd thread and waiting for it to finish as well as using a BackgroundWorker.

The problem I have with this is not having the code run in a different thread, but trying to find a way to wait for it to finish.

Basically, Right now I do the following:

  1. Connect to the database
  2. Create a background worker (or thread) to do the writing to the database (I will probably end up with the BackgroundWorker so I can use the ReportProgress
  3. Start the thread or BackgroundWorker
  4. Use a While loop to wait for the thread / BackgroundWorker to finish. For the thread, I wait for IsAlive to become false, for the BackgroundWorker, I toggle a boolean variable.
  5. I let the user know the process is finished.

The problem is in #4.

Doing a while loop with no code in it, or a Thread.Sleep(0) leaves the UI blocked (Thread.Sleep(0) makes the program take 100% of the program resources as well)

So I do:

while (!thread.IsAlive)
   Thread.Sleep(1);

-or-

while (bProcessIsRunning)
   Thread.Sleep(1);

which blocks the UI.

If I call Application.DoEvents() there, the UI is updated (though it's clickable, so I have to disable the entire form while this process runs).

If I run the process synchronously, I still need to create some sort of way for the UI to be updated (in my mind, a DoEvents call) so it doesn't appear locked up.

What am I doing wrong?

Heredity answered 16/11, 2011 at 19:22 Comment(7)
Welcome to Stack Overflow. Couple of little things: 1. Please don't prefix your titles with stuff like "C# .NET (3.0)". That's what tags are used for on Stack Overflow. 2. No signatures, please. 3. Speaking of tags, you should tell us whether you're using winforms or wpf or whatever by adding the appropriate tag.Lifework
Sorry, I wasn't sure what to tag it with and with the 5 tag cap, I didn't put it. I'm using Winforms (which is now a tag)Heredity
@JohnSaunders: I strongly disagree with your suggestion to not include tags (e.g. C# .NET WinForms) in the title. REASON: Google is the most common way that people reach a stackoverflow page. Having a title that includes such context makes it MUCH quicker to decide whether a Q&A is relevant.Neuroblast
@ToolmakerSteve: you're mistaken. Stack Overflow already puts the top tag into the title as seen by Google.Lifework
@JohnSaunders: So what. (1) one tag isn't enough. (2) if you've clicked through to the page, for whatever reason, you now are looking at the title itself. Why in god's name would you want to reduce the usefulness of the title? Wrong.Neuroblast
@ToolmakerSteve: go read the linked article. There's pretty clear consensus on this, and on the subject of extra noise in general. And I, and many others, think that adding metadata to the title reduces the value of an otherwise clear title. Why go from "Waiting for a long process and still updating UI" to "C# Winforms Waiting for a long process and still updating UI"?Lifework
@JohnSaunders: IF the tags were listed immediately under the title, that POV would be sensible. But they aren't. This is NOT "NOISE"! The notion that one should have to scan down to the end of a possibly very long question in order to find out crucial context for the question, to know whether one cares about the question or not, is simply wrongheaded.Neuroblast
M
4

C# uses an event model -- what you want to do is dispatch the process that does the work and then have that process fire a custom event when done or use one of the threading events. While the process is running in the "background" release control back to the system from your code.

Maas answered 16/11, 2011 at 19:24 Comment(5)
Does this mean that I should start the BackgroundWorker and then leave all code execution until the RunWorkerCompleted fires? Doesn't that mean the user could still interact with the form (same as a DoEvents), while the process runs? It also increases the need for global variables (such as the database connection) and would require significantly more code if the calling functions are in a class, not on the form itself.Heredity
There are a number of ways to deal with this. Easy is to disable interface and put up a spinner or some other visual. Then tear it down when the async fires. Other options are to only "freeze" controls or handle responses differently while the background is going on.Maas
I don't know why this would require more code, it should be the case that closures should allow you to use local variables in your thread. Would you like to see an example of this?Maas
I've created events in my class that use the BackgroundWorker delegates. That way I can just pass the information through without any processing. I'll create a progress bar or spinner for the form while the process goes. thanks!Heredity
@TrevorWatson Exactly. Perfect. Good luck.Maas
S
7

Firstly, why do you wish to avoid DoEvents()?

Secondly, you're using conflicting terms.

waiting == blocking

You say you don't want to block the UI thread, but you do want to wait for the task to finish. These are mutually exclusive states. If you're waiting for something to finish, you're blocking your thread.

If you want the UI to actually be usable (not blocked), then you don't wait for your task to finish. Just register an event handler to fire when it finishes. For instance with a BackgroundWorker, handle the RunWorkerCompleted event. For a Task, you can use a continuation to dispatch a callback to your main thread.

But it seems you just want the UI to be updated, not usable. Usually that only makes sense if you want a progress bar or some other UI animation to keep moving. In that case, I would open a modal dialog, kick off my task, and then wait on it while, yes, calling DoEvents().

    var dialog = new MessageBoxFormWithNoButtons("Please wait while I flip the jiggamawizzer");
    dialog.Shown += (_, __) =>
        {
            var task = Task.Factory.StartNew(() => WriteToDatabase(), TaskCreationOptions.LongRunning);
            while (!task.Wait(50))  // wait for 50 milliseconds (make shorter for smoother UI animation)
                Application.DoEvents(); // allow UI to look alive
            dialog.Close();
        }

    dialog.ShowDialog();

The modal dialog prevents the user from doing anything, but any animation will still work because of DoEvents() being called 20 times a second (or more).

(You would probably want to add special handling for different task completion states, but that's off-topic.)

Stutzman answered 16/11, 2011 at 20:6 Comment(0)
M
4

C# uses an event model -- what you want to do is dispatch the process that does the work and then have that process fire a custom event when done or use one of the threading events. While the process is running in the "background" release control back to the system from your code.

Maas answered 16/11, 2011 at 19:24 Comment(5)
Does this mean that I should start the BackgroundWorker and then leave all code execution until the RunWorkerCompleted fires? Doesn't that mean the user could still interact with the form (same as a DoEvents), while the process runs? It also increases the need for global variables (such as the database connection) and would require significantly more code if the calling functions are in a class, not on the form itself.Heredity
There are a number of ways to deal with this. Easy is to disable interface and put up a spinner or some other visual. Then tear it down when the async fires. Other options are to only "freeze" controls or handle responses differently while the background is going on.Maas
I don't know why this would require more code, it should be the case that closures should allow you to use local variables in your thread. Would you like to see an example of this?Maas
I've created events in my class that use the BackgroundWorker delegates. That way I can just pass the information through without any processing. I'll create a progress bar or spinner for the form while the process goes. thanks!Heredity
@TrevorWatson Exactly. Perfect. Good luck.Maas
K
0

You cannot wait on the UI thread.

Instead, add a handler to the Exited event.

Karyolymph answered 16/11, 2011 at 19:24 Comment(0)
C
0

I don't know if this will simplify your issue, but we use the Essential Objects controls: http://www.essentialobjects.com/Products/EOWeb/ to manage our long processes.

Consumerism answered 16/11, 2011 at 19:26 Comment(2)
Unfortunately, we can't purchase any additional components at this time. I do like some of their web interface components though.Heredity
The progress component is free.Consumerism
M
0

If you delegate the long db operation to a backgroundworker thread then progress is signalled by firing the ProgressChangedEvent from worker, you handle that say updating a progress bar in the UI. Equally finished is signalled by firing RunWorkerCompleteEvent.

No polling required / looping Do events required.

The question is while your background thread is doing something what are you not allowed to do in the form.

Close it, change an edit box? etc. That's done through some sort of state machine could be somethings as simple as you disable a button when you kick the thread off and then enable it again in the RunWorkerCompleted event. Of you could leave the buutom alone and check a boolean called busy in it's Click handler.

Are you offloading the process to show progress or simply to avoid "Windiows is not responding" or are there other things the user can sensibly do while it's in mid process, such as close the form, cancel the operation etc.

Once you've figured out what the UI should be doing you can hook the backgrondworker events into some code that will manage things.

Manis answered 16/11, 2011 at 20:0 Comment(1)
It's mainly for the "Window is not responding" message, but at some point there should be some notification to the user that a process is running so they don't think we've crashed i suppose.Heredity

© 2022 - 2024 — McMap. All rights reserved.