views:

126

answers:

3

Hi folks, i have the following issue: In asynchronous context i need to initialize fields of some custom object before i can proceed with other operations on it, so i do:

class ContainingObject
{    
   private CustomObject _co;

   SomeMethod()
   {
     _co = new CustomObject();
     _co.InitObjectAsyncCompleted += (s,e) => DoStuff();
     _co.InitObjectAsync();
   }    
}

class CustomObject
{
   public string Field1, Field2, Field3, Field4;

   public EventHandler InitObjectAsyncCompleted;

   public void InitObjectAsync()
   {
   }    
}

The catch is that fields are also initialized through asynchronous calls to WCF service, and all must be initialized before i raise the InitObjectAsyncCompleted event. There is quite a number of those fields, each is initialized with different WCF call, and implying i cannot change the WCF part for now, i see two ways to solve the problem:

1) Chain WCF calls, so first call initializes first field, then calls WCF to initialize second field, and so on before all fields are initialized, then i raise "completed" event in last WCF call.

public void InitObjectAsync()
{  
    var proxy = new ProxyFactory.GetCustomObjectProxy;
    proxy.GetDataForField1Completed += (s,e) => 
    { 
        Field1 = e.Result;
        proxy.GetDataForField2Completed += (s1,e1) => 
        { 
          Field2 = e1.Result; 
          //keep this up building a chain of events, when Field4 is filled, raise
          // InitObjectAsyncCompleted(this, null);          
        };
        proxy.GetDataForField2();
    };
    proxy.GetDataForField1();
} 

2) Since i know how many method calls should be completed, 4 in this case, i can make a counter.

public void InitObjectAsync()
{  
    int counter = 0;
    var proxy = new ProxyFactory.GetCustomObjectProxy;
    proxy.GetDataForField1Completed += (s,e) => 
    { 
        Field1 = e.Result;
        if(counter >= 3)
            InitObjectAsyncCompleted(this, null);
        else
            counter++;
    };
    proxy.GetDataForField1();
    proxy.GetDataForField2Completed += (s,e) => 
    { 
        Field2 = e.Result;
        if(counter >= 3)
            InitObjectAsyncCompleted(this, null);
        else
            counter++;
    };
    proxy.GetDataForField2();
    //repeat for all fields
}

I don't really like either of solutions, first one builds a pretty big and badly readable chain of events, second is just... crude - can anyone suggest a more elegant way of solving this problem?

+3  A: 

If you use the Parallel extensions for .NET 4.0 you can create several asynchronous tasks and join them very easily:

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);
Johann Blais
Good approach, if there will be more similar tasks to warrant addition of Parallel Extensions, might try it out too
æther
I like this approach better than mine. You could always code the relevant classes yourself!
Lunivore
A: 

Put each WCF call in a little wrapper class. Put those classes in a set (or list if order is important), and make them remove themselves from the set when the call is finished. They should also pulse a Monitor.

Monitor.Enter. Loop through all the WCF calls in the set. Then wait on the Monitor. Every time you get a notification, if the set isn't empty, wait. When you get out of the wait loop, call init and raise the event. You can always time out on the Monitor.Wait if you want to (I often call my locks waitingRoom so it's obvious what's going on).

If you isolate yourself from the fact that it's WCF calls you're waiting on then this is nice and easy to test, too, and you can do things like log any WCF call which fails by identifying it through the wrapper class.

Lunivore
Was thinking of such solution, but setting a timeout timer for cases where some calls might not return somehow makes me uneasy for using such approach
æther
+1  A: 

Your second approach is a bit easier to understand than the first, but both approaches are a bit fragile.

One alternative is to track the number of outstanding initialization requests and completions, and use this information to decide when to trigger the event. Here's an example of what I mean:

private int _outstandingRequests = 0;

public void InitObjectAsync()
{
    RequestField( proxy.GetDataForField1,
                  proxy.GetDataForField1Completed, 
                  s => Field1 = s );

    RequestField( proxy.GetDataForField2, 
                  proxy.GetDataForField2Completed,
                  s => Field2 = s );

    RequestField( proxy.GetDataForField3, 
                  proxy.GetDataForField3Completed,
                  s => Field3 = s );
    // ... and so on...
}

// This method accepts two actions and a event handler reference.
// It composes a lambda to perform the async field assignment and internally
// manages the count of outstanding requests. When the count drops to zero,
// all async requests are finished, and it raises the completed event.

private void RequestField<T>( Action fieldInitAction, 
                              EventHandler fieldInitCompleteEvent,
                              Action<T> fieldSetter )
{
    // maintain the outstanding request count...
    _outstandingRequests += 1;

    // setup event handler that responds to the field initialize complete        
    fieldInitCompleteEvent += (s,e) =>
    {
        fieldSetter( e.Result );

        _outstandingRequests -= 1;

        // when all outstanding requests finish, raise the completed event
        if( _outstandingRequests == 0 )
           RaiseInitCompleted();
    }

    // call the method that asynchronously retrieves the field value...
    fieldInitAction();
}

private void RaiseInitCompleted()
{
    var initCompleted = InitObjectAsyncCompleted;
    if( initCompleted != null )
        initCompleted(this, null);
}
LBushkin
Thanks, transparent and flexible solution there
æther
You might want to use interlocking mechanisms to avoid concurrency issues on the increment and decrement operations. Interlocked.Increment(...) is a nice method to do that
Johann Blais