如何使用C#任务并行库和IProducerConsumerCollection实现通用回调?

时间:2013-03-19 17:44:27

标签: c# .net generics task-parallel-library blockingcollection

我有一个组件向基于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>

1 个答案:

答案 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
    }
}