views:

355

answers:

2

The following code contains a few nested async calls within some foreach loops. I know the silverlight/wcf calls are called asyncrously -but how can I ensure that my wcfPhotographers, wcfCategories and wcfCategories objects are ready before the foreach loop start? I'm sure I am going about this all the wrong way -and would appreciate an help you could give.

    private void PopulateControl()
    {

        List<CustomPhotographer> PhotographerList = new List<CustomPhotographer>();

        proxy.GetPhotographerNamesCompleted += proxy_GetPhotographerNamesCompleted;
        proxy.GetPhotographerNamesAsync();


        //for each photographer
        foreach (var eachPhotographer in wcfPhotographers)
        {

            CustomPhotographer thisPhotographer = new CustomPhotographer();

            thisPhotographer.PhotographerName = eachPhotographer.ContactName;
            thisPhotographer.PhotographerId = eachPhotographer.PhotographerID;

            thisPhotographer.Categories = new List<CustomCategory>();

            proxy.GetCategoryNamesFilteredByPhotographerCompleted += proxy_GetCategoryNamesFilteredByPhotographerCompleted;
            proxy.GetCategoryNamesFilteredByPhotographerAsync(thisPhotographer.PhotographerId);


            // for each category
            foreach (var eachCatergory in wcfCategories)
            {

                CustomCategory thisCategory = new CustomCategory();

                thisCategory.CategoryName = eachCatergory.CategoryName;
                thisCategory.CategoryId = eachCatergory.CategoryID;

                thisCategory.SubCategories = new List<CustomSubCategory>();

                proxy.GetSubCategoryNamesFilteredByCategoryCompleted += proxy_GetSubCategoryNamesFilteredByCategoryCompleted;
                proxy.GetSubCategoryNamesFilteredByCategoryAsync(thisPhotographer.PhotographerId,thisCategory.CategoryId);

                // for each subcategory
                foreach(var eachSubCatergory in wcfSubCategories)
                {
                    CustomSubCategory thisSubCatergory = new CustomSubCategory();

                    thisSubCatergory.SubCategoryName = eachSubCatergory.SubCategoryName;
                    thisSubCatergory.SubCategoryId = eachSubCatergory.SubCategoryID;
                }


                thisPhotographer.Categories.Add(thisCategory);
            }

            PhotographerList.Add(thisPhotographer);
        }

        PhotographerNames.ItemsSource = PhotographerList;
    }




    void proxy_GetPhotographerNamesCompleted(object sender, GetPhotographerNamesCompletedEventArgs e)
    {
        wcfPhotographers = e.Result.ToList();
    }


    void proxy_GetCategoryNamesFilteredByPhotographerCompleted(object sender, GetCategoryNamesFilteredByPhotographerCompletedEventArgs e)
    {
        wcfCategories = e.Result.ToList();
    }

    void proxy_GetSubCategoryNamesFilteredByCategoryCompleted(object sender, GetSubCategoryNamesFilteredByCategoryCompletedEventArgs e)
    {
        wcfSubCategories = e.Result.ToList();
    }
+1  A: 

Yes, before you can proceed with the next step of the algorithm, you need to have gotten the result of the previous step, which can be hard when you have to use the async methods.

If this is not happening on the UI thread, then you could just block and wait for the response. For example, have each "completed" method signal (using whatever synchronization primitives are available in Silverlight; I don't know offhand e.g. if ManualResetEvent is there, if so, have the completed callback call .Set()), and then have your main PopulateControl method invoke the FooAsync() call and then block until the ManualResetEvent signals (by calling .Wait()).

If this is on the UI thread and you really need to write a non-blocking solution, then it is much, much harder to code this up correctly in C#. You might consider using F# instead, where asyncs provide a nice programming model for non-blocking calls.

EDIT:

Pseudo-code example to block for results:

// class-level
ManualResetEvent mre = new ManualResetEvent(false);
// some method that needs to make WCF call and use results
void Blah() {
    // yadda yadda
    proxy.FooCompleted += (o,ea) => { ... mre.Set(); };
    proxy.FooAsync(...);
    mre.WaitOne();  // block until FooCompleted
    // use results from FooCompleted now that they're here
    // mre.Reset() if you intend to use it again later
}

I used a lambda for FooCompleted, but using a separate method like you have is fine too.

Brian
Peter St Angelo
no that didnt work unfortunaelty -it hangs on mre.Wait();
Peter St Angelo
Note that if you use it more than once, you'll have to .Reset() it between calls.
Brian
A: 

Alternatively, for each async method you are using to populate the collection you can create a helper method that would return IObservable and then use Linq query to group the result.

E.g.:

private IObservable<Photographer> GetPhotographerNames()
{
    var photographers = Observable
        .FromEvent<GetPhotographerNamesCompletedEventArgs>(proxy, "GetPhotographerNamesCompleted")
        .Prune()
        .SelectMany(e => e.EventArgs.Result.ToObservable());

    proxy.GetPhotographerNamesAsync();

    return photographers;
}

And similarly:

private IObservable<Category> GetCategoryNamesFilteredByPhotographer(int photographerId)     { ... }
private IObservable<SubCategory> GetSubCategoryNamesFilteredByCategory(int photographerId, int categoryId) { ... }

Now you can write a Linq query:

var pcs = from p in GetPhotographerNames()
          from c in GetCategoryNamesFilteredByPhotographer(p.PhotographerId)
          from s in GetSubCategoryNamesFilteredByCategory(p.PhotographerId, c.CategoryId)
          select new {p, c, s};

This query will return you a list of triplets (Photographer, Category, SubCategory) Now all you have to do is to Subscribe to it and aggregate it to the objects you use on the client which should be pretty straightforward.

PL
hi thank you for suggestion -which framework/silverlight version does this target ?
Peter St Angelo
Will work in SL3 but requires download of Reactive Extensions (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx)
PL