Вызывать событие, когда все вызовы асинхронных методов завершены - PullRequest
3 голосов
/ 08 сентября 2010

у меня есть следующая проблема: В асинхронном контексте мне нужно инициализировать поля некоторого пользовательского объекта, прежде чем я смогу продолжить другие операции с ним, поэтому я делаю:

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()
   {
   }    
}

Загвоздка в том, что поля также инициализируются через асинхронные вызовы службы WCF, и все они должны быть инициализированы до того, как я вызову событие InitObjectAsyncCompleted. Существует довольно много этих полей, каждое из которых инициализируется различными вызовами WCF, и подразумевается, что пока я не могу изменить часть WCF, я вижу два пути решения проблемы:

1) Цепные вызовы WCF, поэтому сначала вызов инициализирует первое поле, затем вызывает WCF для инициализации второго поля и т. Д., Прежде чем все поля будут инициализированы, затем я вызываю событие «завершено» в последнем вызове WCF.

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) Так как я знаю, сколько вызовов методов должно быть выполнено, в этом случае я могу сделать счетчик.

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
}

Мне не очень нравится ни одно из решений, во-первых, строится довольно большая и плохо читаемая цепочка событий, во-вторых, просто ... грубо - кто-нибудь может предложить более элегантный способ решения этой проблемы?

Ответы [ 3 ]

3 голосов
/ 08 сентября 2010

Если вы используете расширения Parallel для .NET 4.0, вы можете создать несколько асинхронных задач и очень легко присоединиться к ним:

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);
1 голос
/ 08 сентября 2010

Ваш второй подход немного проще для понимания, чем первый, но оба подхода немного хрупкие.

Одна альтернатива - отслеживать количество ожидающих запросов и завершений инициализации и использовать эту информацию для принятиякогда вызвать событие.Вот пример того, что я имею в виду:

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);
}
0 голосов
/ 08 сентября 2010

Поместите каждый вызов WCF в небольшой класс-оболочку.Поместите эти классы в набор (или список, если порядок важен), и заставьте их удалить себя из набора, когда вызов завершен.Они также должны пульсировать монитор.

Monitor.Enter.Переберите все вызовы WCF в наборе.Затем подождите на мониторе.Каждый раз, когда вы получаете уведомление, если набор не пуст, подождите.Когда вы выйдете из цикла ожидания, вызовите init и вызовите событие.Вы всегда можете установить тайм-аут на Monitor.Wait, если хотите (я часто называю свои блокировки waitingRoom, поэтому очевидно, что происходит).

Если вы изолируете себя от того факта, что WCF вызывает васожидая, тогда это тоже приятно и легко проверить, и вы можете делать такие вещи, как записывать любой вызов WCF, который не удался, идентифицируя его через класс-обертку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...