views:

2620

answers:

6

I am running into issues when trying to convert all my normal WCF calls to async WCF calls. I'm finding I have a refactor a lot of code and not sure exactly how to do it. I have used the method that I found here but running into issues where I need things to happen in order.

private void btnSave_Click(object sender, RoutedEventArgs e)
{

  List<Item> itemList = GetList();
  foreach(Item i in itemList)
  {
    DoSomeWork(i);

    if(i.SomeID == 0)
    {      
       DoSomeMoreWork(i);  
    }

    UpdateRecord(i)  // this can't execute until the above code is complete

  }
}

private void DoSomeWork(Item i)
{
  // call async method
}

private void DoSomeMoreWork(i)
{
  // call async method
}

private void UpdateRecord(item i)
{
  // call async method
}

What is the best way to refactor code to work in an asyncronous way, or do I need to completely rethink my logic? Do I really have to insert counters and switches everywhere to make sure certain things are done before other things execute?

EDIT: The reason I'm doing this is in the next couple months, we are converting this WPF application to Silverlight, which requires async calls. So I'm trying to convert our regular WCF calls to async in preparation. I'm finding it requires a different way of thinking.

+1  A: 

I am perhaps a bit puzzled as to why you need to use asynchronous WCF operations when you need things to be synchronous inside the loop.

If you are just using the async methods to help keep the UI from hanging, then you could just use a BackgroundWorker that supports progress updates to keep the UI up to date, and not use Async WCF calls.

You should also be able to call your various functions from the Completed events for the Async methods.

Just hook up event handlers to the completed events and then pass your Item object as the userState parameter when you start the async WCF call. This way you will have it as a parameter when each of the Completed events fires. That way you will only be doing the next step in your processing as the previous async call completes.

I don't know if this really is answering your question though.

Navaar
At least one reason for using async methods when what you'd really prefer is synchronous: when you're on Silverlight, *everything* has to be asynchronous. This is generally for good reasons, but it *is* a PITA.
Ken Smith
+1 for Ken Smith's comment; I hit the same wall http://stackoverflow.com/questions/1286864/silverlight-wcf-proxy-async-only
Andrei Rinea
+1  A: 

Take a look at Juval Lowy's (author of Programming WCF Services) website for examples of how to achieve asynchronous programming in WCF. The downloads are free; you just have to provide your email address.

Matt Davis
A: 

Add 2 properties to Item called SomeWorkDone and SomeMoreWorkDone both as booleans. Create methods to handle both DoSomeWorkCompleted and DoSomeMoreWorkCompleted. In those methods, set the respective boolean properties to true and call UpdateRecord. Within UpdateRecord, ensure that both Done properties are true and then complete the calls.

You'll have some possible contention issues but this should get you going.

Chris Porter
Unless I'm misunderstanding, I don't think this would work. You either need to block one method until the other method(s) complete (in which case you an use a ResetEvent of some sort), or you need to call one method when the other method finishes in a sort of callback.
Ken Smith
A: 

If you're not using Silverlight, you can block your thread in one method until the other methods complete, using, say, a ManualResetEvent. But that won't work in Silverlight, since all WCF calls happen on the main UI thread, so if you block that thread, everything blocks. A better approach is to do something like this, using callbacks:

    public delegate void OperationCallback();

    private void btnSave_Click(object sender, RoutedEventArgs e)
    {

        List<Item> itemList = GetList();
        foreach (Item i in itemList)
        {
            DoSomeWork(i, () =>
            {
                if (i.SomeID == 0)
                {
                    DoSomeMoreWork(i, () =>
                    {
                        UpdateRecord(i);
                    });
                }
                else
                {
                    UpdateRecord(i);
                }
            });

        }
    }

    private void DoSomeWork(Item i, OperationCallback callback)
    {
        // call async method then callback when it completes.
        callback();
    }

    private void DoSomeMoreWork(Item i, OperationCallback callback)
    {
        // call async method, then callback when it completes.
        callback();
    }

    private void UpdateRecord(Item i)
    {
        // call async method
    }

It's certainly not as clear as the synchronous version, but if you use lambda expressions as much as possible, it's still possible to keep the control flow fairly readable.

Ken Smith
+3  A: 

For what you're doing, I'd say the real place to handle things is to make a single call to the service per item, not 3.

Preferably, if the list of items is not huge, make a single call to the service with the whole list...

private void btnSave_Click(object sender, RoutedEventArgs e)
{  
    List<Item> itemList = GetList();  
    foreach(Item i in itemList)  
    {    
        DoAllTheWorkAndUpdate(i);    
    }
}

or...

private void btnSave_Click(object sender, RoutedEventArgs e)
{  
    List<Item> itemList = GetList();  
    foreach(Item i in itemList)  
    {    
        if(i.Id == 0)
        {
            DoLotsOfWorkAndUpdate(i);
        }
        else
        {
            DoSomeWorkAndUpdate(i);
        }

    }
}

or...

private void btnSave_Click(object sender, RoutedEventArgs e)
{  
    List<Item> itemList = GetList();  
    DoTheWorkOnTheWholeList(itemList);
}

In other words, it feels like some of your responsibilities may be misplaced - I generally prefer to make services where I can make a single call to them. Then, the asynchronous nature is irrelevant, because you're not performing a sequence of events.

kyoryu
urm so what if you're calling 3 separate services on 3 separate servers? well most likely 2, but since i'm playing devils advocate i'll say 3. for instance I have to create a payment profile in Authorize.NET's CIM web service and then send the result to my own service.
Simon_Weaver
Well, in that case you'd obviously have to make at leat n calls, where n is the number of separate services. I'd still try to keep it to a single call per individual service. In addition, if you're passing data from one service to another, it might be a sign of misplaced responsibilities - having one of the services contact the other for you may be a better solution. That may not always be possible, of course, but requiring consumer code to keep two other services in sync is something that needs to be done carefully - lots of room for error.
kyoryu
+1  A: 

Try using this

http://ayende.com/Blog/archive/2008/03/29/WCF-Async-without-proxies.aspx

the approach that definitely works.

bsnote