【问题标题】:API Design for Timeouts: TimeoutException or boolean return with out parameter?超时的 API 设计:TimeoutException 或带 out 参数的布尔返回?
【发布时间】:2011-11-23 22:07:57
【问题描述】:

该场景是基于消息队列的 RPC - 由于底层机制是异步的,客户端应指定在超时之前他们希望等待响应的时间。作为客户端,你更愿意使用这两个代码 sn-ps 中的哪一个?

最重要的是:作为 GetResponseTo() 方法的用户,为什么您更喜欢其中一个?您的选择如何使您的代码更具扩展性、可读性、可测试性等?

try
{
    IEvent response = _eventMgr.GetResponseTo(myRequest, myTimeSpan);
    // I have my response!
}
catch(TimeoutException te)
{
    // I didn't get a response to 'myRequest' within 'myTimeSpan'
}

IEvent myResponse = null;

if (_eventMgr.GetResponseTo(myRequest, myTimeSpan, out myResponse)
{
    // I got a response!
}
else
{
    // I didn't get a response... :(
}

供您参考,下面是 GetResponseTo() 的当前实现:

public IEvent GetResponseTo(IEvent request, TimeSpan timeout)
{
    if (null == request) { throw new ArgumentNullException("request"); }

    // create an interceptor for the request
    IEventInterceptor interceptor = new EventInterceptor(request, timeout);

    // tell the dispatcher to watch for a response to this request
    _eventDispatcher.AddInterceptor(interceptor);

    // send the request
    _queueManager.SendRequest(request);

    // block this thread while we wait for a response.  If the timeout elapses,
    // this will throw a TimeoutException
    interceptor.WaitForResponse();

    // return the intercepted response
    return interceptor.Response;
}

【问题讨论】:

    标签: c# .net api timeout


    【解决方案1】:

    第一个也不是第二个,我想使用Task Parallel Library,这是从 .NET 4.5 开始异步执行所有操作的推荐方式:

    Task<IEvent> task = _eventMgr.GetResponseToAsync(myRequest);
    
    if (task.Wait(myTimeSpan))
    {
        // I got a response!
    }
    else
    {
        // I didn't get a response... :(
    }
    

    【讨论】:

    • 由于方法调用确实是同步的,使用 TPL 没有意义,@dtb:你觉得呢?
    • 底层机制是异步的;我认为试图将这一事实隐藏在一个一旦超时、网络错误等发生就会泄漏的抽象背后没有任何价值。但是,使用 TPL 是否有意义,这在一定程度上取决于消息队列库。
    • AFAIK 一些企业消息传递库同时提供异步和同步方法 API,因此调用者可以考虑在每种特定情况下使用哪一个,因此同步和异步方法都有意义
    • 为什么消息队列库会有所作为?它是 RabbitMQ 的 .NET 客户端。
    • 您需要将库的操作映射到 TPL 构造。如果这不可能或太难,那么 TPL 显然不适合。我不熟悉 RabbitMQ 的 .NET 客户端。
    【解决方案2】:

    您可以使用 AutoResetEvent 类来处理第二个管道。

    尽量避免你的第一个代码 sn-p 因为异常很昂贵

    【讨论】:

    • 如果您通过通常情况下通过异常的方法循环很多次,则异常会很昂贵。如果不是通常的情况下应该使用异常
    • 是的,EventInterceptor.WaitForResponse() 的实现实际上是在等待一个 AutoResetEvent。
    【解决方案3】:

    我个人更喜欢例外版本。如果我指定一些超时,我的意见是这是一个例外,那么如果我无法在指定的时间跨度内得到结果。我不认为基于事件的通知是最好的决定。以下逻辑取决于结果,因此对我来说没有意义。 但是,如果您也想提供异步方法,那么 Task 是一个好主意,就像 dtb 所说的那样

    【讨论】:

      【解决方案4】:

      异常繁杂,每个 API 方法调用都应该被 try/catch/finally 包裹起来,以处理自定义异常。这种方法对开发人员不友好,所以我不喜欢它。

      考虑到GetResponse() 调用本身对于 API 使用者来说是同步的——返回操作值是很正常的,但我建议引入一些更抽象和信息丰富的东西,而不是简单的布尔状态,这样你就可以返回提供的任何状态对于底层消息传递系统,这可能是自定义错误代码、消息,甚至是对象。因此,由于这也是 API - put 接口:

      enum OperationStatus
      {
         Unknown,
         Timeout,
         Ok
      }
      
      // pretty simple, only message and status code
      interface IOperationResult<T>
      {
            OperationStatus Status { get; }
            string Message { get; }
            T Item { get; }      
      }
      
      
      class GetResponseResult : IOperationResult<IEvent>
      {
         ...
      } 
      
      class EventManager
      {
           public IOperationResult<IEvent> GetResponseTo(
                                            IRequest request, 
                                            TimeSpan timeInterval)
          {    
              GetResponseResult result;  
      
              // wait for async request                 
              // ...
      
              if (timeout)
              {
                result = new GetResponseResult 
                              { 
                                 Status = OperationStatus.Timeout,
                                 Message = underlyingMessagingLib.ErrorMessage
                              };
              }
              else
              {
                result = new GetResponseResult 
                              { 
                                 Status = OperationStatus.Ok,
                                 Item = response
                              };
              }
      
              return result;
          }
      }
      

      【讨论】:

      • GetResponseTo() 方法的实现会阻塞您的线程 - 使它看起来好像它发送了请求并同步获得了响应 - 即使这一切都发生在异步 pub/sub 上。
      • 另外,如果我按照您的建议暴露了一个事件,则由您决定哪个请求超时(可能您有许多未完成的请求)。
      • @John Ruiz:EventArgs 可以为您提供有关超时请求、缓冲区同步 API 的信息,这和 TPL 都没有意义
      • 感谢您提供代码!这会起作用,我不会说错的,但我不喜欢它的是我通过引入 IOperationResult 和 OperationStatus 使我的 API 变得更加复杂。通过使用 TPL,我的客户只需要学习 IEventManager 和 IEvent。
      【解决方案5】:

      我已选择使用 out 参数。

      我想将其他人标记为答案,但我无法这样做。根据我在 cmets 中链接的问题/答案,我尝试实施基于 TPL 的方法,但无法这样做。

      我不想像@sll 建议的那样,通过引入更多概念来混淆我的事件模型。

      尽管@dasheddot 更喜欢异常版本,@sll 有一个很好的观点,即尝试发送一堆请求并在循环中获得一堆响应的人可能必须处理很多异常。

      // potentially 10 exceptions?  meh... let's not go down this road.
      for(int i=0;i<10;i++)
      {
        try
        {
           IEvent response = _eventMgr.GetResponseTo(myRequest, myTimeSpan);
      
           // I have my response!
        }
        catch(TimeoutException te)
        {
           // I didn't get a response to 'myRequest' within 'myTimeSpan'
        } 
      }
      

      【讨论】:

        猜你喜欢
        • 2019-11-28
        • 2014-01-12
        • 2013-04-18
        • 1970-01-01
        • 1970-01-01
        • 2021-10-19
        • 1970-01-01
        • 2016-09-18
        • 1970-01-01
        相关资源
        最近更新 更多