用于限制同时异步调用次数的模式

时间:2010-04-09 05:08:17

标签: vb.net asynchronous limit throttling

我需要从外部系统中检索多个对象。外部系统支持多个同时请求(即线程),但是可以泛洪外部系统 - 因此我希望能够异步检索多个对象,但我希望能够限制同时异步请求的数量。即我需要检索100个项目,但不希望一次检索超过25个项目。当25的每个请求完成时,我想触发另一个检索,一旦它们全部完成,我想按照它们被请求的顺序返回所有结果(即,在返回整个调用之前没有返回结果的点)。对于这类事情,有没有推荐的模式?

这样的事情是否恰当(伪代码,显然)?

  private List<externalSystemObjects> returnedObjects = new List<externalSystemObjects>;

  public List<externalSystemObjects> GetObjects(List<string> ids)
  {
      int callCount = 0;
      int maxCallCount = 25;
      WaitHandle[] handles;

      foreach(id in itemIds to get)
      {
          if(callCount < maxCallCount)
          {
               WaitHandle handle = executeCall(id, callback);
               addWaitHandleToWaitArray(handle)
          }
      else
      {
           int returnedCallId = WaitHandle.WaitAny(handles);
           removeReturnedCallFromWaitHandles(handles);
      }
   }

   WaitHandle.WaitAll(handles);

   return returnedObjects;
   }

   public void callback(object result)
   {
         returnedObjects.Add(result);
   }

2 个答案:

答案 0 :(得分:1)

考虑要作为队列处理的项目列表,25个处理线程从中排队任务,处理任务,添加结果然后重复,直到队列为空:

 class Program
  {
    class State
    {
      public EventWaitHandle Done;
      public int runningThreads;
      public List<string> itemsToProcess;
      public List<string> itemsResponses;
    }

    static void Main(string[] args)
    {
      State state = new State();

      state.itemsResponses = new List<string>(1000);
      state.itemsToProcess = new List<string>(1000);
      for (int i = 0; i < 1000; ++i)
      {
        state.itemsToProcess.Add(String.Format("Request {0}", i));
      }

      state.runningThreads = 25;
      state.Done = new AutoResetEvent(false);

      for (int i = 0; i < 25; ++i)
      {
        Thread t =new Thread(new ParameterizedThreadStart(Processing));
        t.Start(state);
      }

      state.Done.WaitOne();

      foreach (string s in state.itemsResponses)
      {
        Console.WriteLine("{0}", s);
      }
    }

    private static void Processing(object param)
    {
      Debug.Assert(param is State);
      State state = param as State;

      try
      {
        do
        {
          string item = null;
          lock (state.itemsToProcess)
          {
            if (state.itemsToProcess.Count > 0)
            {
              item = state.itemsToProcess[0];
              state.itemsToProcess.RemoveAt(0);
            }
          }
          if (null == item)
          {
            break;
          }
          // Simulate some processing
          Thread.Sleep(10);
          string response = String.Format("Response for {0} on thread: {1}", item, Thread.CurrentThread.ManagedThreadId);
          lock (state.itemsResponses)
          {
            state.itemsResponses.Add(response);
          }
        } while (true);

      }
      catch (Exception)
      {
        // ...
      }
      finally
      {
        int threadsLeft = Interlocked.Decrement(ref state.runningThreads);
        if (0 == threadsLeft)
        {
          state.Done.Set();
        }
      }
    }
  }

您可以使用异步回调执行相同操作,无需使用线程。

答案 1 :(得分:0)

拥有一些类似队列的结构来保存待处理的请求是一种非常常见的模式。在可能存在多个处理层的Web应用程序中,您会看到“漏斗”式方法,处理更改的早期部分具有更大的队列。可能还有某种优先级应用于队列,更高优先级的请求被混洗到队列的顶部。

在您的解决方案中需要考虑的一件重要事情是,如果请求到达率高于您的处理速率(这可能是由于拒绝服务攻击,或者只是处理的某些部分在今天异常缓慢),那么您的队列将无限制地增加。您需要有一些策略,例如在队列深度超过某个值时立即拒绝新请求。