如何等待收到几个回调

时间:2012-09-05 08:26:45

标签: c# events

我有这样的代码:

    public void IssueOrders(List<OrderAction> actions)
    {
        foreach (var action in actions)
        {
            if (action is AddOrder)
            {
                uint userId = apiTransactions.PlaceOrder((action as AddOrder).order);
                Console.WriteLine("order is placing userId = " + userId);
            }
            // TODO: implement other actions
        }
        // how to wait until OnApiTransactionsDataMessageReceived for all userId is received?

        // TODO: need to update actions with received data here
    }

    private void OnApiTransactionsDataMessageReceived(object sender, DataMessageReceivedEventArgs e)
    {
        var dataMsg = e.message;
        var userId = dataMsg.UserId;

apiTransactions.PlaceOrder是异步的,因此我收到userId作为结果,但我将在回调OnApiTransactionsDataMessageReceived中接收数据。

例如,如果我下3个订单,我会收到3个userId,例如134。现在我需要等到收到所有这些userId的数据。

如果这很重要,

userId总是在增加。这几乎是整数序列,但由于并行执行,可能会省略一些数字。

UPD 注意:

  • 可以从不同的线程并行执行IssueOrders
  • 可以在PlaceOrder返回
  • 之前调用callack

UPD2

可能我需要重构下面的PlaceOrder代码,以便我可以保证在收到“回调”之前知道userId

    public uint PlaceOrder(Order order)
    {
        Publisher pub = GetPublisher();

        SchemeDesc schemeDesc = pub.Scheme;
        MessageDesc messageDesc = schemeDesc.Messages[0]; //AddMM
        FieldDesc fieldDesc = messageDesc.Fields[3];
        Message sendMessage = pub.NewMessage(MessageKeyType.KeyName, "FutAddOrder");

        DataMessage smsg = (DataMessage)sendMessage;
        uint userId = counter.Next();
        FillDataMessageWithPlaceOrder(smsg, order, userId);

        System.Console.WriteLine("posting message dump: {0}", sendMessage);
        pub.Post(sendMessage, PublishFlag.NeedReply);
        sendMessage.Dispose();

        return userId;
    }

所以我需要将PlaceOrder拆分为两个方法:userId CreateOrdervoid PostOrder。这将保证在收到回叫时我知道userId

3 个答案:

答案 0 :(得分:1)

我会在Reactive Framework中查看ForkJoin方法。它将阻塞,直到多个异步调用完成。

编辑:似乎ForkJoin()只包含在Rx的实验版中。这是基于Merge()的所需内容discussion

答案 1 :(得分:0)

最愚蠢和最有效的方法之一是:

public void IssueOrders(List<OrderAction> actions)
{
    var userIds = new List<uint>();
    lock(theHashMap)
        theHashMap[userIds] = "blargh";

    foreach (var action in actions)
    {
        if (action is AddOrder)
        {
            lock(userIds)
            {
               uint userId = apiTransactions.PlaceOrder((action as AddOrder).order);
               Console.WriteLine("order is placing userId = " + userId);

               userIds.Add(userId);
            }
        }

        // TODO: implement other actions
    }

    // waiting:
    do
    {
       lock(userIds)
          if(userIds.Count == 0)
             break;

       Thread.Sleep(???); // adjust the time depending on how long you wait for a callback on average

    }while(true);

    lock(theHashMap)
        theHashMap.Remove(userIds);

    // now you have the guarantee that all were received
}

private Dictionary<List<uint>, string> theHashMap = new Dictionary<List<uint>,string>();

private void OnApiTransactionsDataMessageReceived(object sender, DataMessageReceivedEventArgs e)
{
    var dataMsg = e.message;
    var userId = dataMsg.UserId;

    // do some other things

    lock(theHashMap)
        foreach(var list in theHashMap.Keys)
           lock(list)
              if(list.Remove(userId))
                 break;
}

但是,这是非常粗暴的方法..除非你通过等待解释你的意思,否则很难提出更多建议 - 正如Jon在评论中所说的那样。例如,您可能想要离开IssueOrders,等待任何地方,并确保在所有工作到达时完成一些额外的工作?或者你可能不能离开IssueOrders除非收到所有人?等。

编辑:请注意,在ADD附近,锁定必须在之前 PlaceOrder,否则,当回调达到超高速时,回调可能会在添加之前尝试删除ID。另请注意,此实现非常简单:回调必须每次搜索并锁定所有列表。使用一些额外的字典/地图/索引,它可能会进行很多优化,但为了便于阅读,我没有这样做。

答案 2 :(得分:0)

如果您能够更改API,请考虑使用Task Parallel Library,您的代码将变得更加容易。

否则AutoResetEvent可能会对您有所帮助:

    private Dictionary<int, AutoResetEvent> m_Events = new ...;

    public void IssueOrders(List<OrderAction> actions)
    {
        foreach (var action in actions)
        {
            if (action is AddOrder)
            {
                uint userId = apiTransactions.PlaceOrder((action as AddOrder).order);
                // Attention: Race condition if PlaceOrder finishes                                         
                // before the MRE is created and added to the dictionary!
                m_Events[userId] = new ManualResetEvent(false);
                Console.WriteLine("order is placing userId = " + userId);
            }
            // TODO: implement other actions
        }

        WaitHandle.WaitAll(m_Events.Values);

        // TODO: Dispose the created MREs
    }

    private void OnApiTransactionsDataMessageReceived(object sender, DataMessageReceivedEventArgs e)
    {
        var dataMsg = e.message;
        var userId = dataMsg.UserId;

        m_Events[userId].Set();
    }