Progress Feedback From Slow WCF Call in Silverlight

I’ve got a slow process in WCF and there’s not much I can do about speeding it up (it’s crawling a website), but it’s so slow that the Silverlight application times out – which isn’t what I want. This can be solved by increasing the timeout-time for the all (and indeed you still have to do that with the solution I present), but I’d like to be able to show a window that says “Currently doing X” with updates until the process is done.

As an added benefit of this method, the user can also be allowed to prematurely abort the process.

I’m using this technique for crawling websites as part of our SEO Rules Site Audit service.

There are three basic ways of solving the problem;

Discrete work steps

Either the WCF call returns after one or several “work items” are performed, expecting the Silverlight application to call the WCF method again asking it to perform the next batch of work items. This works great and I’ve used it too great success, but this can be fairly costly if the work items are short (there’s a round-trip for each step of the process) or if the process can’t easily be divided into discrete steps that be be restarted. Using this method it’s fairly straight forward to let the user cancel the process. You simply don’t request the continuation of the process.

Out-of-band information

The other method is that the WCF call runs uninterrupted for the entire process but updates some out-of-band information, a process state, that the Silverlight application “polls” on a regular basis. The backend process must update the information so that there’s new interesting information for the Silverlight application to publish.

This post will demonstrate this second method.

The process state

The process state will contain list of strings that describe the latest action(s) that have been taken by the process (limited to 10 in this demonstration), it also contains a description field that’s supposed to be a bit more detailed.

It could also contain any other information that you wanted to show to the user, like progress information and such, but that’s outside of the scope of this demo.

Using a BusyIndicator

This is the code for my slow process (a WCF call) – it doesn’t really do anything except sleep for 50 milliseconds a number of times.

[OperationContract]
public string SlowProcess(Guid progressFeedbackKey, int inputVariable)
{
    ProgressInfo info = _monitor.RegisterNewProgressProcess(progressFeedbackKey);

    info.AddMessage("Starting");
    const int max = 300;
    int tens = -1;

    for (int i = 0; i < max; i++)
    {
        double percentage = (double)i / max;
        int currentTens = (int)Math.Round(percentage * 10) * 10;
        if (tens != currentTens)
        {
            info.AddMessage("Reached {0}%", currentTens);
        }
        tens = currentTens;

        // This part is what generates the feedback
        info.Description = string.Format("At {0:0.00%}", percentage);
        Thread.Sleep(50);

        // Every once in a while, check if the user requested an abort
        if (info.Abort)
        {
            throw new ProcessAbortedException();
        }
    }

    return "SlowProcess 1 is DONE! Variable was " + inputVariable;
}

This code shows a busy indicator that’s updated every 250 ms with the latest message from the server.

private void SlowProcessWithBusyIndicatorClick(
  object sender,
  System.Windows.RoutedEventArgs e)
{
    BusyIndicator.IsBusy = true;

    BusyIndicator.BusyContent = "Starting...";
    Service1Client client = new Service1Client();
    client.InnerChannel.OperationTimeout = new TimeSpan(0, 0, 5, 0);
    client.SlowProcessCompleted +=
        (object sndr, SlowProcessCompletedEventArgs args) =>
        {
            BusyIndicator.IsBusy = false;
            _slowProcessDone = true;
            label.Content = args.Result;
        };

    client.SlowProcessAsync(_slowProcessProgressFeedbackKey, 10);
    RequestFeedback(_slowProcessProgressFeedbackKey);
}

private void RequestFeedback(Guid progressFeedbackKey)
{
    if (_slowProcessDone == false)
    {
        Service1Client client = new Service1Client();

        client.GetProcessInfoCompleted +=
            (a, b) =>
            {
                if (_slowProcessDone == false)
                {
                    BusyIndicator.BusyContent = b.Result.Description;
                    RequestFeedback(progressFeedbackKey);
                }
            };

        client.GetProcessInfoAsync(progressFeedbackKey, 500);
    }
}

And this is what it looks like action;

image

 

Using a ProgressFeedBackWindow

I’ve created a window that’s intended for longer running processes where you wish to see more feedback. The window also supports aborting the call (which is supported by SlowProcess above). The code for running the process with the window looks like this;

private void SlowProcessWithWindowClick(
    object sender,
    System.Windows.RoutedEventArgs e)
{
    ProgressFeedbackWindow feedbackWindow =
        new ProgressFeedbackWindow(
            "Running SlowProcess through WCF - with ProgressFeedback!!");

    Service1Client client = new Service1Client();
    client.InnerChannel.OperationTimeout = new TimeSpan(0, 0, 5, 0);
    client.SlowProcessCompleted +=
        (object sndr, SlowProcessCompletedEventArgs args) =>
        {
            feedbackWindow.ProcessDone();
            if (feedbackWindow.Aborted == false)
            {
                label.Content = args.Result;
            }
            else
            {
                label.Content = "USER ABORTED!";
            }
        };

    client.SlowProcessAsync(feedbackWindow.ProgressFeedbackKey, 10);
    feedbackWindow.Show();
}

Using the window to make the call instead looks like this

image

Download the source and demo project from CodePlex.

Advertisements