1

I want to show a new Form with a Progressbar while my MainForm is doing some database related work. I've read that you don't want to pass the UI to a new thread, so I'm wondering what the best solution is to get around this problem?

I can't do the database related work on another thread since it uses a dozen calls to the UI and to the MainForm. I also can't use Application.DoEvents() since the MainForm's thread is working inside a class, and as far as I know you shouldn't do any calls to the parent of the class.

Any suggestions would be appreciated!

Edit

To clarify what I was actually wondering:

My Program looks kinda like this:

... Content = DatabaseClass.LoadContent();
ApplyContentToUI(Content);

And what I wanted to do was the following:

//Show a form here with a progressbar
... Content = DatabaseClass.LoadContent();
ApplyContentToUI(Content);
//Close the form here

But since the time to execute ApplyContentToUI was aprox. 1 second I descided to do the following:

//Show a form here with a progressbar
... Content = DatabaseClass.LoadContent();
//Close the form here
ApplyContentToUI(Content);

If you, unlike me, need to show the Progressbar while reloading the UI I suggest that you look at the comments and answers below.

Robin
  • 1,927
  • 3
  • 18
  • 27
  • 8
    Having a dozen calls to the UI in the business logic is worse than calling `Application.DoEvents()`. You should separate your logic from your UI. – SLaks Jul 08 '13 at 16:04
  • Can you gather all the data you need from the UI up front then throw that over the fence to a background thread where you do the work? If not your whole design seems broken. – i_am_jorf Jul 08 '13 at 16:06
  • Doesn't `BackgroundWorker` work a lot better for this than `Application.DoEvents`? Or are we talking pre-2.0 code here? –  Jul 08 '13 at 16:10
  • @HighCore - I don't see any reasons to be condescending. I have separated the logic from the UI, however the progressbar is supposed to appear when the UI is loading. Which makes UI-calls a necessity. – Robin Jul 08 '13 at 16:13
  • 3
    @Robin The fact that you've stated you can't do your background tasks in a background thread because it's accessing the UI is specifically stating that you *are* mixing your UI with your business logic. You shouldn't be doing that. You should absolutely be accessing the DB in a background thread and using some existing synchronization mechanism to allow the UI to be updated without mixing the UI and business logic. The `BackgroundWorker` can do it, the Task Parallel Library can as well, you can use the `Progress` class, or a number of other related mechanisms. – Servy Jul 08 '13 at 16:16
  • @Robin look at the [background worker class](http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.95).aspx). It provides an event for you on your UI thread to update your UI to provide feedback. – Tombala Jul 08 '13 at 16:16
  • @HighCore So your answer to my problem is that "if you do it right from the beginning, no problems will occur"? Then we might as well close this sight since it's existence is redundant – Robin Jul 08 '13 at 16:25
  • Anyhow, I will try what @Servy suggested and use a BackgroundWorker. Thanks for the help – Robin Jul 08 '13 at 16:27
  • @HighCore Well doesn't that make it my employer's problem then? You do realize that the purpose of this site is to allow people to share knowledge with eachother, and by stating that you should possess the knowledge before you come here is some what illogical. – Robin Jul 08 '13 at 16:34
  • 1
    @Robin The more appropriate point that he should have stated is that you shouldn't be asking how to get away with mixing your UI logic and business logic and get your program to run anyway; if you don't know how to separate the two then learn how to do that, asking here if you need the help. It's not that you should magically know how to separate the UI from DB logic, it's simply that learning how to do it should be your new question. – Servy Jul 08 '13 at 16:40
  • @Servy Agreed. That sounds like a good solution. I've added an answer that summarizes your solution. Thanks! – Robin Jul 08 '13 at 16:52
  • Whatever class you use to do database work could be raising events to indicate progress. That way, it does not have to reference any GUI element directly. So, your first task is to replace any code that references GUI elements within the class that does the database work. Once this is done, you can then run the database work on a separate thread. Events should be raised using Control.BeginInvoke(). The GUI should respond to the events and update whatever controls to reflect progress. – Tarik Jul 08 '13 at 17:01
  • Edited post to clarify. Sorry for being unclear. – Robin Jul 08 '13 at 17:05

2 Answers2

9

I can't do the database related work on another thread

False.

Recommendation:

As many have mentioned in the comments you should refactor the code to separate the business logic from the UI logic. There are known patterns and practices for each .NET version that will lead to a clean separation. I have put together some tools and links to their usages for you to review.

Tools:

BeginInvoke

BeginInvoke is used to run code asynchronously on the thread that the control originates from. This will allow UI operations on occur in the case the originating thread is the UI thread. BackgroundWorker uses this to ReportProgress. This will allow the UI to remain responsive while long running operations occur.

This should not be embedded directly in the business logic. However, it can be used to relay messages back to the UI thread.

See:

BackgroundWorker

  • BackgroundWorker will run work on a separate thread and it has event driven progression (.NET 2.0+).

The BackgroundWorker is an event driven control that will execute code on another thread. It has the built in capability to notify progress to listeners using ReportProgress.

Behind the scenes the BackgroundWorker uses Control.BeginInvoke for ReportProgress which allows effective separation of business logic.

See:

Dispatcher

  • The Dispatcher can relay operations to the UI thread (.NET 3.0+);

The Dispatcher was added for WPF, but it can still be used in WinForms (although it is less desirable). The Dispatcher will function in the same way as BeginInvoke and it can run both Synchronously or Asynchronously.

See:

Task

  • The Task(T) class makes it simple to interweave logic off the UI thread. (.NET 4.0+, .NET 3.5+ with TPL package).

The Task Parallel Library was added to provide a set of abstractions for parallelism and concurrency. Task is a message driven and allows for encapsulation of a non-blocking operation. This is also the basis for .NET 4.5 async/await pattern.

Task is used to stream operations such that they can be linked together and produce a result. This is more streamlined than the BackgroundWorker and it allows for better composition of operations. This also making implementing producer-consumer scenarios simpler.

See:

Community
  • 1
  • 1
Dustin Kingen
  • 20,677
  • 7
  • 52
  • 92
  • 5
    1) This isn't really explaining to the OP *how* to use those techniques to solve his problems. Just mentioning them is really a comment, not an answer. 2) This wouldn't be addressing the fact that the OP is mixing their UI logic with their business logic; in fact it's encouraging them to mix it that much more. 3) Even if you *can* use Dispatcher in winforms, it's not really an appropriate practice. – Servy Jul 08 '13 at 16:22
  • Whatever OP will do to invoke a member...IMO the problem is that in his/her business layer/model "...it uses a dozen calls to the UI...". He/she can't move anywhere if he doesn't **fix this first** (I didn't understand if he can change that code or not). Otherwise the only option I see is to **fake** controls used by his model, move it to another thread and use dispatching (BeginInvoke is perfect without any WPF stuff) to call UI code from the background thread. – Adriano Repetti Jul 08 '13 at 16:25
  • 1
    @Adriano The first thing the OP really needs to do is separate the business logic from the UI logic. His comments thus far indicate he doesn't even know *how* to do that, so the first thing that needs to happen is for us to help him learn how it should be done (at least going forward). If it turns out that he explicitly states that doing that isn't an option in this case, then move on to the less desirable "hacks" as an alternative, once you're sure the ideal solution isn't an option. Skipping the "appropriate" solution and going right to the poorly designed hacks isn't a good answer. – Servy Jul 08 '13 at 16:27
  • 1
    @HighCore I suppose he just can't move that code (because of too large refactoring or he doesn't own that). That's why I suggest to create **facade UI controls** (if possible don't even derive them from Control) that dispatch calls to UI thread. – Adriano Repetti Jul 08 '13 at 16:28
  • @Adriano How do you know that it's too large or that he doesn't own it? Wait until he says he can't do that before suggesting a dramatically less desirable solution. – Servy Jul 08 '13 at 16:29
  • @Servy I completely agree, I assumed he doesn't do it because he can't but...well maybe not. – Adriano Repetti Jul 08 '13 at 16:30
  • @Tarik You should post that as a comment to the original question, not one of the answers that is already overrun by comments. You could even put it in as an answer if you're willing to put in some references, code, etc. – Tombala Jul 08 '13 at 16:40
  • Sorry for being unclear, but I didn't mean that the Development Environment didn't allow me to "do the database related work on another thread" as much as that I don't have the time required to redo the program. – Robin Jul 08 '13 at 16:58
  • @Servy I have updated my answer to factor in your points. If there is anything else that is missing then I would like to know. – Dustin Kingen Jul 10 '13 at 11:54
1

@Servy suggested that instead of mixing UI and logic, I would instead do the logic first and then apply the result to the UI. Although this does not fulfills the original purpose it seems to be the easiest way around my problem. (The Form with a Progressbar will only be visible while doing the logic, but now while updating the UI.) The UI-update will be done in aprox. a second so the Progressbar is not really necessary in the end, due to the short period of time.

For those who still require a Progressbar while loading the UI, some good answers/solutions were given under this question.

Robin
  • 1,927
  • 3
  • 18
  • 27