开发者

Raise event when all asynchronous method calls are complete

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 开发者_StackOverflow社区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?


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);


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);
}


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜