超时的API设计:TimeoutException或带out参数的布尔返回

本文关键字:out 参数 返回 或带 布尔 TimeoutException API 设计 超时 | 更新日期: 2023-09-27 18:15:38

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

最重要的是:作为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;
}

超时的API设计:TimeoutException或带out参数的布尔返回

既不是第一也不是第二,我想使用任务并行库,这是从。net 4.5开始做所有异步事情的推荐方式:

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

您可以使用AutoResetEvent类,这将处理第二个管道。

尽量避免你的第一个代码片段,因为异常是昂贵的

我个人更喜欢例外版本。如果我指定一些超时,我的意见是,这是一个异常,然后如果我不能在指定的时间范围内得到结果。我不认为基于事件的通知是最好的选择。下面的逻辑取决于结果,所以对我来说没有意义。但如果你也想提供异步方法,任务是一个好主意,如dtb

所述

异常是沉重而混乱的,每个API方法调用都应该由try/catch/finally包装以处理自定义异常。这种方法对开发人员不友好,所以我不喜欢它。

考虑到GetResponse()调用本身对于API消费者是同步的-返回操作值是很正常的,但我建议引入一些更抽象和信息的东西,而不是简单的bool状态,因此您可以返回底层消息传递系统提供的任何状态,这可以是自定义错误代码,消息,甚至对象。因为这也是一个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;
    }
}

我选择使用out参数。

我想标记别人的答案,但我不能这样做。我试图实现基于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'
  } 
}