4

I have a very simple task that loads some data like so:

async Task<List<Invoice>> GetInvoices()
{
    var invoices = await Task.Run(() => db.Invoices.AsNoTracking().ToList());
    return invoices;
}

What would be the best way to use this with a progressbar? I don't need to get the percentage loaded (although that would be useful), I simply need to show/hide the progressbar at start and finish of loading data. Is there a way to create a completed or finished method perhaps?

I am very new to async/await so kid gloves please!

user1166905
  • 2,612
  • 7
  • 43
  • 75
  • 4
    But..You're waiting on the Task with await, just set the state of the progressbar to indeterminate at the start, and stop it after the await. – Patrick Oct 09 '14 at 19:07
  • I typically pass a callback to the background task/worker that it can use to report progress, with the callback scheduling a progress indicator update on the UI thread. But, seeing as your task is a single statement, I'm not sure if you have a convenient way of signalling incremental progress. May be better to use an indeterminate wait indicator as @Patrick suggested. – Mike Strobel Oct 09 '14 at 19:22
  • You should be using `ToListAsync` to execute a query asynchronously rather than executing the query synchronously in another thread. – Servy Oct 09 '14 at 19:33

3 Answers3

13

Assuming GetInvoices gets called in some UI event handler and there is some progress bar control called ProgressBar, it's really simple:

private async void OnButton1Click(..)
{
    ProgressBar.IsIndeterminate = true;
    var result = await GetInvoices();
    ProgressBar.IsIndeterminate = false; // Maybe hide it, too
    // TODO: Do stuff with result
}

If you want to show actual progress, take a look at Progress<T> and IProgress<T>.

Sebastian Negraszus
  • 11,915
  • 7
  • 43
  • 70
2

You already have what you need. To illustrate here's a simple example with tasks and using await, and ContinueWith.

using System;
using System.Threading.Tasks;
using System.Diagnostics;
public class Program
{
    public static void Main()
    {
        Task t = new Program().TestTasks();
        Console.WriteLine("hello");
        t.Wait();
    }

    public async Task TestTasks()
    {
        Stopwatch watch = Stopwatch.StartNew();
        Console.WriteLine("1." + watch.ElapsedMilliseconds);
        // await task
        int res = await TestAsync();
        Console.WriteLine("2." + watch.ElapsedMilliseconds + " " + res);
        // save task and wait on it
        Task<int> t = TestAsync();
        t.ContinueWith((r) =>
        {
            Console.WriteLine("4." + watch.ElapsedMilliseconds + " " + r.Result);
        });
        Console.WriteLine("3." + watch.ElapsedMilliseconds);
        t.Wait();
    }

    public static async Task<int> TestAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(2));
        return 42;
    }
}

This prints:

1.0
hello
2.2009 42
3.2009
4.4013 42

Note how the await leaves the method and prints "hello" in the Main method, and after (about) 2 seconds continue in the TestTasks method. On the other hand, if we do not await, as the second call to TestAsync shows, 3. is printed immediately, and after 2 seconds, 4. is printed.

With this in mind, you can just call your method GetInvoices with await since you're returning a Task.

// Set state of progressbar to indeterminate
List<Invoice> result = await GetInvoices();
// Set state of progressbar to nothing
Patrick
  • 17,669
  • 6
  • 70
  • 85
1

You can do something like:

var task = Task.Run(() => db.Invoices.AsNoTracking().ToList()); 

//show progress

task.ContinueWith(...CLOSE PROGRESS BAR...);

In practise you use Task.ContinueWith construct to catch the termination of the task and hide a progress bar you have shown before.

Note: keep in mind that acting on UI element from another thread requires Invoke() call, as that call redirects actual invokation to the main thread, which is a only thread were UI controls can be operated on windows.

Tigran
  • 61,654
  • 8
  • 86
  • 123
  • this means the ProgressBar should not be shown modally? Hope that suits the OP's requirement. – King King Oct 09 '14 at 19:04
  • @KingKing: why not? you 1) start computation on another thread 2) show dialog 3) catch termination of the thread 4) invoke close dialog. – Tigran Oct 09 '14 at 19:07
  • I understand that the `//show progress` is the placeholder for code showing the progressbar dialog there. So if it's shown modally the `task.ContinueWith` won't be executed. Of course if creating a new thread there, it's OK. – King King Oct 09 '14 at 19:13
  • I did not downvote this answer. Looks like you don't get what I meant. I mean I understand the `//show progress` comment in your code is a placeholder for such as this code `progressBarDialog.ShowDialog();`. Then this will block the current thread and `task.ContinueWith` won't be called. I think the code showing progressbar dialog can be placed after `task.ContinueWith`. – King King Oct 09 '14 at 20:12
  • @KingKing: if you read documentation about Task.Complete, is says: "Creates a continuation that executes asynchronously when the target Task completes." So it will be called. – Tigran Oct 09 '14 at 20:22
  • do you mean in this snippet `someWindow.ShowDialog(); task.ContinueWith(some_task);`. The execution can jump to `task.ContinueWith` while the `someWindow` is showing modally? It's weird to me. At least the current thread execution is blocked right at `.ShowDialog()`, after closing that dialog, the execution can jump to `task.ContinueWith`. – King King Oct 09 '14 at 20:27
  • @KingKing: it's not current thread execution blocked, it *main* thread execution blocked, meanwhile other thread is running under and terminates under it, raising *async* call to `ContinueWith`. – Tigran Oct 09 '14 at 20:31
  • Sorry maybe I'm missing something I've never encountered before. As far as I know when you call `.ShowDialog()`, ***every line of code*** after that can't be executed until the dialog is closed. – King King Oct 09 '14 at 20:34
  • Please view this test of mine http://www.mediafire.com/download/75ifm1fsuacnf5m/TaskTest.zip Please run the test, the dialog is showing, and see if you can wait for the `MessageBox` in `ContinueWith` to show. Right after closing that dialog, all the code following will be executed. I doubt you misunderstand me much. I know how to show a dialog while still running other code. Also I'm not really new to multi-threading. – King King Oct 09 '14 at 21:01
  • @KingKing: you calling in serial way all the functions. it's correct that you are not able to show message box after dialog. my point was, that you can call them on OnComplete call: like you do "Done" message box. – Tigran Oct 13 '14 at 12:33