3
\$\begingroup\$

Let's say that I have a method that makes an HTTP request that will take a long time to finish. While that's happening the UI is not updating because I'm doing it in the UI's thread.

One solution is to:

  1. Show the "Loading" animation.
  2. Start the HTTP request on a thread.
  3. On the completion callback stop the "Loading" animation and work on the response.

That's all nice and good, but it means that I went from code that was sequential in nature to something that has callbacks or something similar (promises).

So, I'm trying to keep the sequential nature while updating the UI. Having something like await/async would help here, but even in that case the caller has to be aware of this (plus I need .NET 4.5, let's assume that this is not an option). So I came up with something that keep the sequential nature of the original call, doesn't affect the caller of the function makes the HTTP request but updates the UI.

So, here it is:

public class Async<T>
{
    public static T Run(Func<T> action)
    {
        var worker = new BackgroundWorker();
        var error = new Exception[] {null};
        var result = new[] {default(T)};

        // The original call will run in a separate thread.
        worker.DoWork += (sender, ea) => { result[0] = action(); };
        worker.RunWorkerCompleted += (sender, ea) => { error[0] = ea.Error; };
        worker.RunWorkerAsync();

        // While the work is being done in a separate thread, update the UI's event loop (is this safe???)
        while (worker.IsBusy)
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
            Thread.Sleep(20);
        }
        // Re raise the exception if needed
        if (error[0] != null)
            throw error[0];

        return result[0];
    }
}


//Usage
var someVariable = 123;
var responseA = Async<T>.Run(DoSuperLongTask);
var responseB = Async<T>.Run(() =>
                    {
                        return DoSuperComplicatedTask(someVariable);
                    });

It'll be something like

await Task.Run(() => DoWork());
await Task.Run(() => DoWork());

I'm not an expert in GUIs, and I'm especially not an expert in WPF. So my question to you is:

Is this safe to use?

So far it seems to work okay.

\$\endgroup\$
1
  • \$\begingroup\$ “plus I need .NET 4.5” You don't, you can use async-await on .Net 4.0 using Microsoft.Bcl.Async. But you do need at least VS 2012. \$\endgroup\$
    – svick
    Apr 3, 2014 at 18:56

1 Answer 1

2
\$\begingroup\$

Good you have a BackgroundWorker. If I understood correctly, let's say you want to update a button, whether to enable it or not. you can use this:

delegate void EnableButton(Button b, bool enable);
public void ActuallyEnableButton(Button b, bool enable)
{
    if (b.InvokeRequired)
    {
        EnableButton del = ActuallyEnableButton;
        b.Invoke(del, new object[] { b, enable });
        //in some cases add "return" statement
    }
    b.Enabled = enable;
}

Call ActuallyEnableButton(btn, true) for example, inside DoWork event of BackgroundWorker.

This applies to Label, TextBox, ComboBox, ListView, ProgressBar, etc. No need to use this code if you want to update ToolStripStatusLabel and ToolStripProgressBar.

Hope this helped.

\$\endgroup\$
2
  • \$\begingroup\$ Why NOT need to use this code if I want to update ToolStripStatusLabel and ToolStripProgressBar ? \$\endgroup\$
    – Kiquenet
    Mar 13, 2017 at 16:31
  • \$\begingroup\$ Yup @Kiquenet. You can update those controls directly without a delegate function. \$\endgroup\$
    – tjvg1991
    Mar 20, 2017 at 2:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.