0

Without using extra threads I would simply like to display a "Loading" label or something similar to the user when a large amount of data is being read or written. If I however attempt to modify any UI elements before calling the IO method, the application freezes for a while and then displays the "Loading" message after all the work is already done. This obviously doesn't help. How can I ensure that any UI changes are applied and visible before calling the IO method?

        DataSet ds = STT_Import.ImportExcelToDataSet(filePath);

        bool result = false;

        if (ds != null)
        {
            int cellCount = ds.GetTotalCellCount();

            if (Popup.ShowMessage(string.Format("Your file contains {0} cells. Inserting data will take approximately {1} seconds. Do you want to continue?",
                cellCount, CalculateTime(cellCount)), "Confirm", MessageType.Confirm) == MessageBoxResult.Yes)
            {
                // Tell user the application is working:
                StatusLabel.Content = "Writing to database...";

                // Do actual work after user has been notified:
                result = DB.StoreItems(_currentType, ds);
            }
        }

I tried looking for answers but couldn't find anything that answered my specific question, so I'm sorry if the question has been asked before.

firant
  • 15
  • 4
  • Well there's Application.DoEvents(), but I consider that verboten (it has many issues). I always say you should do multithreading properly - in this case, by using BackgroundWorker. – Matthew Watson Feb 11 '13 at 15:45
  • I agree with Matthew.... Use the BackgroundWorker... http://stackoverflow.com/questions/5483565/how-to-use-wpf-background-worker – PGallagher Feb 11 '13 at 15:46
  • You can't be sure that user will not interact with UI after your long-running IO starts. – Hamlet Hakobyan Feb 11 '13 at 15:46
  • @MatthewWatson Application.DoEvents() is evil. Anyway, how you can call that evil if you querying DB for instance? – Hamlet Hakobyan Feb 11 '13 at 15:48
  • @MatthewWatson `DoEvents` *couldn't* solve this problem, since it's long running blocking IO and not CPU bound work that could inject periodic `DoEvents` calls. – Servy Feb 11 '13 at 15:53
  • I will look into both using a BackgroundWorker and binding a property to the label. I am not so fond of the idea that the user could keep tinkering with the UI while the read/write work is being done, so if BackgroundWorker allows that I'm not sure it's what I'm looking for. – firant Feb 11 '13 at 15:58
  • 2
    @firant Then you need to disable the UI controls before starting the background worker and enable them when it's done. – Servy Feb 11 '13 at 16:07
  • @Servy That's a good idea, I will try this out since binding my label's content to a property didn't do any difference. – firant Feb 11 '13 at 16:20
  • @Servy: If he called DoEvents just after setting StatusLabel.Content and before calling DB.StoreItems() it'd probably update the label display. And then, of course, allow Cthulhu to wake from his dreaming beneath R'lyeh - so it's probably best to bite the bullet and use BackgroundWorker. – Matthew Watson Feb 11 '13 at 18:58
  • @MatthewWatson `Application.DoEvents()` is depreciated in WPF, and has been replaced by the `Dispatcher` or `BackgroundWorker`. – Rachel Feb 11 '13 at 19:41

5 Answers5

1

When working with WPF, you can use the Dispatcher to queue commands on the UI thread at different DispatcherPriorities

This will allow you to queue your long-running process on the UI thread after everything in the DispatcherPriority.Render or DispatcherPriority.Loaded queues have occurred.

For example, your code may look like this:

// Tell user the application is working:
StatusLabel.Content = "Writing to database...";

// Do actual work after user has been notified:
Dispatcher.BeginInvoke(DispatcherPriority.Input,
    new Action(delegate() { 
        var result = DB.StoreItems(_currentType, ds);     // Do Work
        if (result)
            StatusLabel.Content = "Finished";
        else
            StatusLabel.Content = "An error has occured";
     }));

It should be noted though that its usually considered bad design to lock up an application while something is running.

A better solution would be to run the long-running process on a background thread, and simply disable your application form while it runs. There are many ways of doing this, but my personal preference is using the Task Parallel Library for it's simplicity.

As an example, your code to use a background thread would look something like this:

using System.Threading.Tasks;

...

// Tell user the application is working:
StatusLabel.Content = "Writing to database...";
MyWindow.IsEnabled = False;

// Do actual work after user has been notified:
Task.Factory.StartNew(() => DB.StoreItems(_currentType, ds))
    // This runs after background thread is finished executing
    .ContinueWith((e) =>
    {
        var isSuccessful = e.Result;

        if (isSuccessful)
            StatusLabel.Content = "Finished";
        else
            StatusLabel.Content = "An error has occured";

        MyWindow.Enabled = true;
    });
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Your first option still blocks the UI thread. `DB.StoreItems` is a long running method that blocks, so calling it inside of a call to `Dispatcher.BeginInvoke` blocks the UI thread for its duration. You block the UI thread for a slightly shorter interval, but you've not shortened it by *enough* for that to work. Were this a CPU bound task that he had control over you could potentially re-factor it to periodically call `BeginInvoke` and keep the UI responsive, but that's not the case here. For the second bit, you're using another thread, which the OP said is something he doesn't want to do. – Servy Feb 11 '13 at 18:08
  • @Servy That is intentional. The first code sample will update the UI *before* locking up the application, which sounds like what the OP wants to do. And if you read the line right after the first example, I say that it's not recommended to lock up the application in this manner, and suggest reconsidering using a background thread. I recommend TPL for its simplicity, and provide a code sample to go with it. – Rachel Feb 11 '13 at 18:13
0

The Approach you are using is not efficient way so I would suggest to go with Async Programing or threading

Async programming:

Visual Studio 2012 introduces a simplified approach, async programming, that leverages asynchronous support in the .NET Framework 4.5 and the Windows Runtime. The compiler does the difficult work that the developer used to do, and your application retains a logical structure that resembles synchronous code. As a result, you get all the advantages of asynchronous programming with a fraction of the effort. Support .Net framework 4.5
It will save your time to implementing System .Threading and very efficient for the task same as your where we have to wait for some operation

http://msdn.microsoft.com/en-ca/library/vstudio/hh191443.aspx
http://go.microsoft.com/fwlink/?LinkID=261549

or

Threading:

The advantage of threading is the ability to create applications that use more than one thread of execution. For example, a process can have a user interface thread that manages interactions with the user and worker threads that perform other tasks while the user interface thread waits for user input.Support .Net fremework 4.0 or Older

http://msdn.microsoft.com/en-us/library/aa645740%28v=vs.71%29.aspx

Servy
  • 202,030
  • 26
  • 332
  • 449
Garry
  • 4,996
  • 5
  • 32
  • 44
  • Note that for this to work effectively (given the OP's constraints) his DB needs to provide an asynchronous version of the method he needs. – Servy Feb 11 '13 at 15:52
  • If he is already using 4.5 framework it will work otherwise all older version are already supporting Threading – Garry Feb 11 '13 at 15:54
  • I am using Visual Studio 2010 either way, so this wouldn't be possible. – firant Feb 11 '13 at 15:55
  • Just because he's using 4.5 doesn't mean the particular DB framework he's using provides async methods. – Servy Feb 11 '13 at 15:55
  • you need to go with threading as you are using 2010 visual studio you will be using 4.0 framework so Threading is a good way to implement it – Garry Feb 11 '13 at 15:56
  • @Garry Using .NET 4.5 and having a DB framework that provides async methods are two completely independent facts. He could have a database with async methods in .NET 2.0 or a DB without async methods in .NET 4.5. The framework version has nothing to do with it. – Servy Feb 11 '13 at 16:06
0

You are trying to solve the problem in the wrong manner. What you should be doing here is run the time-consuming task in a worker thread; this way, your UI will remain responsive and the current question will become moot.

There are several ways you can offload the task to a worker thread; among the most convenient are using the thread pool and asynchronous programming.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • Asynchronous programming doesn't require doing the work in a non-UI thread. In fact, when the long running task is network IO there is no need for another thread, so that method could be used while meeting the OP's restrictions. – Servy Feb 11 '13 at 16:08
0

It is provably impossible to keep your UI responsive without utilizing additional threads unless your database provides an asynchronous version of the method you're using. If it does provide an asynchronous version of the method then you simply need to use that. (Keep in mind that async does not mean that it's using any other threads. It's entirely possible to create an asynchronous method that never uses additional threads, and that's exactly what's done with most network IO methods.) The specifics of how to go about doing that will depends on the type of DB framework you're using, and how you're using it.

If your DB framework does not provide async methods then the only way to keep the UI responsive is to perform the long running operation(s) in a non-UI thread.

Servy
  • 202,030
  • 26
  • 332
  • 449
0

If you don't want the UI to be responsive I use a busy indicator.
There are prettier cursors - this is an in house application.

using (new WaitCursor())
{
    // very long task
    Search.ExecuteSearch(enumSrchType.NextPage);
}

public class WaitCursor : IDisposable
{
    private Cursor _previousCursor;

    public WaitCursor()
    {
        _previousCursor = Mouse.OverrideCursor;

        Mouse.OverrideCursor = Cursors.Wait;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Mouse.OverrideCursor = _previousCursor;
    }

    #endregion
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176