所有异步方法调用完成后引发事件

时间:2010-09-08 14:00:48

标签: c# wcf events asynchronous

我有以下问题: 在异步上下文中,我需要初始化一些自定义对象的字段,然后才能对其进行其他操作,所以我这样做:

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调用中引发“completed”事件。

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 个答案:

答案 0 :(得分:3)

如果您使用.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 :(得分:1)

你的第二种方法比第一种方法更容易理解,但两种方法都有点脆弱。

一种替代方法是跟踪未完成的初始化请求和完成次数,并使用此信息来决定何时触发事件。这是我的意思的一个例子:

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

答案 2 :(得分:0)

将每个WCF调用放在一个小的包装类中。将这些类放在一个集合(或者如果顺序很重要的列表中),并在调用完成后让它们从集合中删除。他们还应该给监视器发出脉冲。

Monitor.Enter。循环遍历集合中的所有WCF调用。然后在监视器上等待。每次收到通知时,如果该集合不为空,请等待。当你退出等待循环时,调用init并引发事件。你总是可以在Monitor.Wait上超时(如果你愿意的话)(我经常调用我的锁waitingRoom,这样很明显发生了什么。)

如果你把自己与你正在等待的WCF调用这一事实隔离开来,那么这也是很好且易于测试的,并且你可以通过包装类识别它来记录任何失败的WCF调用。