【问题标题】:Raise event when all asynchronous method calls are complete当所有异步方法调用完成时引发事件
【发布时间】:2010-09-08 14:00:48
【问题描述】:

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

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)由于我知道应该完成多少个方法调用,在这种情况下是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
}

我真的不喜欢这两种解决方案,第一个构建了一个相当大且可读性差的事件链,第二个只是......粗略 - 任何人都可以提出一种更优雅的方法来解决这个问题吗?

【问题讨论】:

    标签: c# wcf events asynchronous


    【解决方案1】:

    如果您使用 .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);
    

    【讨论】:

    • 不错的方法,如果有更多类似的任务需要添加并行扩展,也可以尝试一下
    • 我比我更喜欢这种方法。你总是可以自己编写相关的类!
    • 这仅在MethodA/B/C是同步操作时有效。所以这并没有解决响应异步操作完成的问题。使用 Tasks 包装异步方法不会做任何事情。异步方法只是立即返回,并且 Task.WaitAll 立即成功,因为所有方法都已完成。虽然异步操作仍在后台进行。
    【解决方案2】:

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

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

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

    【讨论】:

    • 谢谢,透明灵活的解决方案
    • 您可能希望使用互锁机制来避免增量和减量操作的并发问题。 Interlocked.Increment(...) 是一个很好的方法
    【解决方案3】:

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

    监视器。输入。循环遍历集合中的所有 WCF 调用。然后在监视器上等待。每次收到通知时,如果集合不为空,请等待。当您退出等待循环时,调用 init 并引发事件。您可以随时在 Monitor 上超时。如果您愿意,请稍候(我经常将我的锁称为 waitingRoom,所以很明显发生了什么)。

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

    【讨论】:

    • 正在考虑这样的解决方案,但是为某些呼叫可能无法以某种方式返回的情况设置超时计时器让我对使用这种方法感到不安
    猜你喜欢
    • 2018-05-07
    • 2013-01-27
    • 2016-12-14
    • 1970-01-01
    • 2012-11-23
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多