我有一个组件向基于Web的API提交请求,但必须限制这些请求,以免违反API的数据限制。这意味着所有请求必须通过队列来控制它们的提交速率,但它们可以(并且应该)同时执行以实现最大吞吐量。每个请求必须在完成后的某个时刻将某些数据返回到调用代码。
我正在努力创建一个很好的模型来处理数据的返回。
使用BlockingCollection
我不能只从Task<TResult>
方法返回Schedule
,因为排队和出列的进程位于缓冲区的两端。因此,我创建了一个RequestItem<TResult>
类型,其中包含Action<Task<TResult>>
形式的回调。
这个想法是,一旦一个项目被从队列中拉出,就可以用启动的任务调用回调,但是我已经丢失了那个点的泛型类型参数,我留下了使用反射和各种肮脏的东西(如果它甚至可能)。
例如:
public class RequestScheduler
{
private readonly BlockingCollection<IRequestItem> _queue = new BlockingCollection<IRequestItem>();
public RequestScheduler()
{
this.Start();
}
// This can't return Task<TResult>, so returns void.
// Instead RequestItem is generic but this poses problems when adding to the queue
public void Schedule<TResult>(RequestItem<TResult> request)
{
_queue.Add(request);
}
private void Start()
{
Task.Factory.StartNew(() =>
{
foreach (var item in _queue.GetConsumingEnumerable())
{
// I want to be able to use the original type parameters here
// is there a nice way without reflection?
// ProcessItem submits an HttpWebRequest
Task.Factory.StartNew(() => ProcessItem(item))
.ContinueWith(t => { item.Callback(t); });
}
});
}
public void Stop()
{
_queue.CompleteAdding();
}
}
public class RequestItem<TResult> : IRequestItem
{
public IOperation<TResult> Operation { get; set; }
public Action<Task<TResult>> Callback { get; set; }
}
如何从缓冲区中提取请求并将其提交给API,我如何继续缓冲我的请求,但会向客户端返回Task<TResult>
?
答案 0 :(得分:2)
首先,您可以从Task<TResult>
返回Schedule()
,您只需使用TaskCompletionSource
即可。
其次,为了解决通用性问题,你可以隐藏所有内容(非泛型)Action
。在Schedule()
中,使用完全符合您需要的lambda创建一个动作。消费循环然后将执行该动作,它不需要知道内部是什么。
第三,我不明白你为什么要在循环的每次迭代中开始一个新的Task
。首先,它意味着你实际上不会受到任何限制。
通过这些修改,代码可能如下所示:
public class RequestScheduler
{
private readonly BlockingCollection<Action> m_queue = new BlockingCollection<Action>();
public RequestScheduler()
{
this.Start();
}
private void Start()
{
Task.Factory.StartNew(() =>
{
foreach (var action in m_queue.GetConsumingEnumerable())
{
action();
}
}, TaskCreationOptions.LongRunning);
}
public Task<TResult> Schedule<TResult>(IOperation<TResult> operation)
{
var tcs = new TaskCompletionSource<TResult>();
Action action = () =>
{
try
{
tcs.SetResult(ProcessItem(operation));
}
catch (Exception e)
{
tcs.SetException(e);
}
};
m_queue.Add(action);
return tcs.Task;
}
private T ProcessItem<T>(IOperation<T> operation)
{
// whatever
}
}