我试图创建一个队列来限制和重试对服务的API调用。
伪劣的图表:
队列需要接受多种类型的调用(我认为我已经解决了这个问题),将方法调用及其参数队列存储到API库中。如果调用失败,则需要在队列顶部重试(如图所示)。然后,它需要将数据返回到进行原始调用的程序内的方法(或者回调到另一个方法),即使该调用由于队列而延迟了很长一段时间。一直以来,都没有阻止用户界面。
我对其中的大部分内容感到困惑,除了我将数据返回给原始调用者的部分。或者至少是该类中接受数据的方法(如回调)。
我该怎么做呢?当可以从任意数量的类进行调用时,如何将队列中的数据返回到程序的其余部分?如果我要利用回调,我在哪里/如何将回调信息存储在队列中?
答案 0 :(得分:1)
下面的代码会给你一个想法。
// this is common practice for genertic classes like BaseApiRequest<T> -
// create parent class which does not have generic parameters
public abstract class BaseApiRequest : IDisposable {
public abstract void Dispose();
public abstract void SetException(Exception ex);
}
public abstract class BaseApiRequest<T> : BaseApiRequest {
private readonly ManualResetEventSlim _signal;
private Exception _exception;
private T _result;
protected BaseApiRequest() {
_signal = new ManualResetEventSlim(false);
}
public T GetResult() {
_signal.Wait();
if (_exception != null)
throw new Exception("Exception during request processing. See inner exception for details", _exception);
return _result;
}
public T GetResult(CancellationToken token) {
_signal.Wait(token);
if (_exception != null)
throw new Exception("Exception during request processing. See inner exception for details", _exception);
return _result;
}
public bool TryGetResult(TimeSpan timeout, out T result) {
result = default(T);
if (_signal.Wait(timeout)) {
if (_exception != null)
throw new Exception("Exception during request processing. See inner exception for details", _exception);
result = _result;
return true;
}
return false;
}
public void SetResult(T result) {
_result = result;
_signal.Set();
var handler = ResultReady;
if (handler != null)
handler(this, new ResultReadyEventArgs<T>(_result));
}
public override void SetException(Exception ex) {
_exception = ex;
_signal.Set();
var handler = ResultReady;
if (handler != null)
handler(this, new ResultReadyEventArgs<T>(_exception));
}
public override void Dispose() {
_signal.Dispose();
}
public event EventHandler<ResultReadyEventArgs<T>> ResultReady;
public class ResultReadyEventArgs<T> : EventArgs {
public ResultReadyEventArgs(T result) {
this.Result = result;
this.Success = true;
}
public ResultReadyEventArgs(Exception ex)
{
this.Exception = ex;
this.Success = false;
}
public bool Success { get; private set; }
public T Result { get; private set; }
public Exception Exception { get; private set; }
}
}
这是api请求的可能基类。处理请求时,处理器会调用SetResult。调用者创建请求,将其发布到您的队列,然后有选项:
来电者需要同步结果。然后他调用GetResult,这是一个阻塞调用。如果他不想永远等待出现问题,调用者可以在超时时使用TryGetResult。
如果立即不需要结果,调用者可以订阅ResultReady事件,或者稍后调用GetResult。
现在要处理您的请求,您有几种选择。一种是向BaseApiRequest添加句柄逻辑。然后从处理器中的队列中提取BaseApiRequest,并在其上调用Process。您可能会说请求不应包含处理自身的逻辑,因为它只是请求。然后考虑更复杂的类结构:
public interface IApiRequestHandler {
// type of request which is handled by this handler
Type RequestType { get; }
void Validate(BaseApiRequest request);
void Process(BaseApiRequest request);
}
// specific request
public class SomeDataRequest : BaseApiRequest<int> {
public string Argument1 { get; set; }
public long Argument2 { get; set; }
}
// specific request handler
public class SomeDataRequestHandler : IApiRequestHandler {
public Type RequestType { get { return typeof(SomeDataRequest); } }
public void Validate(BaseApiRequest baseRequest) {
// safe to cast here
var request = (SomeDataRequest) baseRequest;
// validate and throw exception if something is wrong
// no reason to validate when we already started processing
}
public void Process(BaseApiRequest baseRequest) {
// safe to cast here
var request = (SomeDataRequest) baseRequest;
// do processing
request.SetResult(1);
}
}
然后,你的api看起来像这样:
// this should be singleton
public class Api : IDisposable {
private readonly BlockingCollection<BaseApiRequest> _requests = new BlockingCollection<BaseApiRequest>(new ConcurrentQueue<BaseApiRequest>());
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly Dictionary<Type, IApiRequestHandler> _handlers = new Dictionary<Type, IApiRequestHandler>();
public Api() {
// find or explicitly register handlers in some way
// here we just search them in current assembly
foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(c => typeof (IApiRequestHandler).IsAssignableFrom(c))) {
var handler = (IApiRequestHandler) Activator.CreateInstance(type);
if (_handlers.ContainsKey(handler.RequestType))
throw new Exception(String.Format("Request handler for request type {0} already registered.", handler.RequestType));
_handlers.Add(handler.RequestType, handler);
}
new Thread(ProcessingLoop) {IsBackground = true}.Start();
}
private void ProcessingLoop() {
try {
foreach (var request in _requests.GetConsumingEnumerable(_cts.Token)) {
try {
// no casting from object or switches here
_handlers[request.GetType()].Process(request);
}
catch (Exception ex) {
request. SetException(ex);
}
}
}
catch (OperationCanceledException) {
return;
}
}
public void StartProcessing(BaseApiRequest request) {
if (_handlers.ContainsKey(request.GetType()))
throw new Exception("No handlers registered for request type " + request.GetType());
// validate synchronously
_handlers[request.GetType()].Validate(request);
_requests.Add(request);
}
public void Dispose() {
_cts.Cancel();
}
}