views:

644

answers:

3

I have a wcf client that before doing some complicated interactions with the wcf service does a simple check for the service to be alive. As the time-out for a failed connection is one minute, I want to implement this check asynchronously.

This is what I have:

//Method on the main UI thread
public void DoSomeComplicatedInteraction()
{
  If(ConnectionIsActive()) DoTheComplicatedInteraction();
}


 private bool ConnectionIsActive()
    {
        //Status is a Textblock on the wpf form
        Status.Text = "Checking Connection";
        var ar = BeginConnectionIsActive();
        while (!ar.IsCompleted)
        {
            Status.Text += ".";
            Thread.Sleep(100);
        }
        EndConnectionIsActive(ar);
        //IsConnected is a boolean property
        return IsConnected;
    } 

   private IAsyncResult BeginConnectionIsActive()
    {
        //checkConnection is Func<bool> with value CheckConnection
        return checkConnection.BeginInvoke(null,checkConnection);
    }


    private void EndConnectionIsActive(IAsyncResult ar)
    {
        var result=((Func<bool>)ar.AsyncState).EndInvoke(ar);
        IsConnected = result;
    }

  private bool CheckConnection()
    {
        bool succes;
        try
        {
            //synchronous operation
            succes=client.Send(RequestToServer.GetPing()).Succes;
        }
        catch
        {
            succes = false;
        }
        return succes;
    }

This works. Only, when I try to simulate a slow server response by adding a Thread.Sleep in the server's Send method,the UI becomes unresponsive. Moreover, the Status Textblock's Text does not get visibly updated. It seems I need some kind of Application.DoEvents method. Or do I need a different approach?

EDIT: Indeed,a different approach is necessary. Using Thread.Sleep will block the UI when it us called on the Main UI thread. Here is how I solved it:

 //Method on the main UI thread
public void DoSomeComplicatedInteraction()
{
  IfConnectionIsActiveDo(TheComplicatedInteraction);
}

   private void TheComplicatedInteraction()
    {...}

  private void IfConnectionIsActiveDo(Action action)
    {
        Status.Text = "Checking Connection";
        checkConnection.BeginInvoke(EndConnectionIsActive,
        new object[] { checkConnection, action });
    }

private void EndConnectionIsActive(IAsyncResult ar)
{
    var delegates = (object[]) ar.AsyncState;
    var is_active_delegate = (Func<bool>) delegates[0];
    var action = (Action) delegates[1];
    bool is_active=is_active_delegate.EndInvoke(ar);
    IsConnected = is_active;
    Dispatcher.Invoke(DispatcherPriority.Normal,
      new Action<bool>(UpdateStatusBarToReflectConnectionAttempt),is_active);
    if (is_active) action();
}

I am not very happy with it: it spreads (synchronous) code that used to be in two methods into five! This makes the code very messy...

+3  A: 

This is because you're waiting in a loop for the completion of the operation. This loop is done on the main thread, so the UI is frozen. Instead, you should pass an AsyncCallback to BeginInvoke (instead of null), so that you can be notified when the operation completes.

Here's how I would implement it :

public void BeginConnectionIsActive()
{
    AsyncCallback callback = (ar) => 
    {
        bool result = checkConnection.EndInvoke(ar);
        // Do something with the result
    };
    checkConnection.BeginInvoke(callback,null);
}
Thomas Levesque
Thanks, I hoped that the UI would magically respond to user action during the Thread.Sleep, or that an equivalent of Application.DoEvents would exist for WPF. It seems that that is bad solution anyway
Dabblernl
+1  A: 

I can't tell for sure from your code sample where you are invoking this, but in general, you should never do blocking work on the UI thread. This will cause the UI to become unresponsive, as you mention. You must do this work in the background. My guess is that you are running that ConnectionIsActive loop directly on the UI thread which means that UI status updates will essentially be blocked until you return from that method.

It looks like you have the right idea (using asynchronous methods), but the implementation seems overly complex.

Conveniently, WCF allows you to have an asynchronous client-side contract even if the server implementation does not use asynchronous methods. The contracts just need to have the same contract name and be marked specially for the async pattern, as shown in this example:

[ServiceContract(Name = "Ping")]
interface IPing
{
    [OperationContract(IsOneWay = false)]
    void Ping(string data);
}

[ServiceContract(Name = "Ping")]
interface IPingClient
{
    [OperationContract(AsyncPattern = true, IsOneWay = false)]
    IAsyncResult BeginPing(string data, AsyncCallback callback, object state);

    void EndPing(IAsyncResult result);
}

Now on the client side, you can use the IPingClient contract, and simply call client.BeginPing(...). It will return immediately while all the actual work is done in the background, optionally calling you back when it completes.

Your code might then look something like this:

void SomeCodeInUIThread()
{
    // ...
    // Do something to the UI to indicate it is
    // checking the connection...

    client.BeginPing("...some data...", this.OnPingComplete, client);
}

void OnPingComplete(IAsyncResult result)
{
    IPingClient client = (IPingClient)result.AsyncState;
    try
    {
        client.EndPing(result);
        // ...
    }
    catch (TimeoutException)
    {
        // handle error
    }

    // Operation is complete, so update the UI to indicate
    // you are done. NOTE: You are on a callback thread, so
    // make sure to Invoke back to the main form.
    // ...
}
bobbymcr
This approach is very interesting. I tried to find some documentation, but all I found was: "Run svcutil.exe and use the async option". I accepted Thomas' answer because it is more general.
Dabblernl
I would start here: "Synchronous and Asynchronous Operations" http://msdn.microsoft.com/en-us/library/ms734701.aspx
bobbymcr
+1  A: 

Use something like this:

public static class Run
{
    public static void InBackround<TResult>(Func<TResult> operation, Action<TResult> after)
    {
        Async(operation, after, DispatcherPriority.Background);
    }

    private static void Async<TResult>(Func<TResult> operation, Action<TResult> after,
                                               DispatcherPriority priority)
    {
        var disp = Dispatcher.CurrentDispatcher;

        operation.BeginInvoke(delegate(IAsyncResult ar)
        {
            var result = operation.EndInvoke(ar);
            disp.BeginInvoke(priority, after, result);
        }, null);
    }
}

And call it like this:

Run.InBackround(CheckConnection, DoTheComplicatedInteraction);

It takes some delegate, invoke it in background thread, and after it finish gets returned value and calls 'after' delegate in UI thread.

In example above DoTheComplicatedInteraction should looks like:

DoTheComplicatedInteraction(bool connected) {
    if(connected) { proceed... }  else { retry... }
}

If you confused about Dispatcher read Build More Responsive Apps With The Dispatcher article on msdn.

gimalay
Thanks for you answer. This seems quite complex and I have not yet tried it. It does not conditionally invoke the "after" delegate if I am not mistaken.
Dabblernl
sorry for indistinct replay, i added some comments and hope this helps
gimalay
thanks a lot! I will have a look at it.
Dabblernl