当所有异步方法调用完成时触发事件。

3
我有以下问题: 在异步上下文中,我需要初始化某个自定义对象的字段,然后才能继续对其进行其他操作,所以我这样做:
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();
} 

由于我知道应该完成多少方法调用,这里是4个,所以我可以创建一个计数器。
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
如果您使用.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);

如果有更多类似的任务需要添加并行扩展,这是一个不错的方法,也可以尝试一下。 - æther
我喜欢这种方法比我的更好。你总是可以自己编写相关的类! - Lunivore
只有当MethodA/B/C是同步操作时,此方法才有效。因此,这并不能解决响应异步操作完成的问题。使用任务(Task)来包装异步方法没有任何作用。异步方法仅会立即返回,而Task.WaitAll会立即成功,因为所有方法都已经完成。而异步操作仍在后台进行。 - Gavin Williams

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

感谢,这是一个透明且灵活的解决方案。 - æther
你可能想使用互锁机制来避免增量和减量操作的并发问题。Interlocked.Increment(...)是一个很好的方法来实现这一点。 - Johann Blais

0

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

Monitor.Enter。循环遍历集合中的所有WCF调用。然后等待监视器。每次收到通知时,如果集合不为空,则等待。当您退出等待循环时,调用init并引发事件。如果需要,您可以始终在Monitor.Wait上超时(我经常将我的锁称为waitingRoom,以便明确其作用)。

如果您将自己与等待WCF调用的事实隔离开来,那么这也很容易测试,并且您可以通过识别包装类来记录任何失败的WCF调用。


曾考虑过这种解决方案,但为某些调用可能无法返回的情况设置超时计时器使我对使用这种方法感到不安。 - æther

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接