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.

About mfagerlund
Writes code in my sleep - and sometimes it even compiles!

3 Responses to Progress Feedback From Slow WCF Call in Silverlight

  1. Henry Keen says:

    Hi

    Great demonstration, I’ve downloaded the sample and its all working perfectly.

    Unfortunately I’m having some trouble using the same technique in one of my existing projects. For some reason the web service only handles one call at a time so the ‘long process’ method blocks until finished, then the ‘get progress’ method kicks in after.

    I’m wondering if there any special configuration settings that I’m missing in order to make the web service handle concurrent calls?

    Thanks

    • mfagerlund says:

      The web service should handle multiple parallell requests, otherwise every end user would require their own server, so clearly something fishy is going on. Are you using some kind of locking mechanism that’s causing the issue?

      What happens if you replace your actual server code with a simple Thread.Sleep – to verify that things are working as intended in a minimal case? Or you could use my sample code from the post.

      • Henry Keen says:

        As far as I’m aware I’m not using any locking mechanisms on either client or server side; as far as the code is concerned I think its pretty sound. I’ve also tested it with two very simple service methods as you suggested with no luck😦

        I ran through both your example and my project with a debugger and I noticed that in your example a thread is spawned on the server for every web service call that is made.

        In my project, for some reason when two calls are made in succession only one thread is spawned on the server, this suggests to me that there is something wrong with the configuration of my service. I’ve tried all manner of things such as adding a “ServiceBehaviour” attribute to the service (changing the “ConcurrencyMode”, and “InstanceContextMode” values) but the service still seems to only ever queue the requests from the client.

        I’m going to try creating a fresh project with the same web service bindings and see if I get the same behaviour. Thanks for your help so far, I’m open to all suggestions so please let me know if you can think of anything else🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: