Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/293.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何使用C#任务并行库和IProducerConsumerCollection实现通用回调?_C#_.net_Generics_Task Parallel Library_Blockingcollection - Fatal编程技术网

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

如何使用C#任务并行库和IProducerConsumerCollection实现通用回调?,c#,.net,generics,task-parallel-library,blockingcollection,C#,.net,Generics,Task Parallel Library,Blockingcollection,我有一个组件可以向基于web的API提交请求,但必须限制这些请求,以免违反API的数据限制。这意味着所有请求都必须通过队列来控制提交速率,但它们可以(而且应该)并发执行以实现最大吞吐量。每个请求必须在将来完成时的某个时刻向调用代码返回一些数据 我正在努力创建一个好的模型来处理数据的返回 使用BlockingCollection我不能仅仅从Schedule方法返回Task,因为排队和退队过程都在缓冲区的两端。因此,我创建了一个RequestItem类型,其中包含Action形式的回调 其思想是,一

我有一个组件可以向基于web的API提交请求,但必须限制这些请求,以免违反API的数据限制。这意味着所有请求都必须通过队列来控制提交速率,但它们可以(而且应该)并发执行以实现最大吞吐量。每个请求必须在将来完成时的某个时刻向调用代码返回一些数据

我正在努力创建一个好的模型来处理数据的返回

使用
BlockingCollection
我不能仅仅从
Schedule
方法返回
Task
,因为排队和退队过程都在缓冲区的两端。因此,我创建了一个
RequestItem
类型,其中包含
Action
形式的回调

其思想是,一旦从队列中提取了一个项,就可以通过启动的任务调用回调,但到那时,我已经丢失了泛型类型参数,剩下的就是反射和各种各样的肮脏(如果可能的话)

例如:

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; }
}
公共类请求调度器
{
私有只读BlockingCollection _queue=new BlockingCollection();
公共请求调度程序()
{
这个。Start();
}
//这无法返回任务,因此返回void。
//相反,RequestItem是通用的,但在添加到队列时会出现问题
公共作废时间表(请求项请求)
{
_添加(请求);
}
私有void Start()
{
Task.Factory.StartNew(()=>
{
foreach(变量项在_queue.GetConsumingEnumerable()中)
{
//我希望能够在这里使用原始类型参数
//有没有一种不经思考的好方法?
//ProcessItem提交HttpWebRequest
Task.Factory.StartNew(()=>ProcessItem(项目))
.ContinueWith(t=>{item.Callback(t);});
}
});
}
公共停车场()
{
_queue.CompleteAdding();
}
}
公共类请求项:IRequestItem
{
公共操作操作{get;set;}
公共操作回调{get;set;}
}
当请求从缓冲区中提取并提交到API时,我如何继续缓冲请求,但将任务返回给客户端?

首先,您可以从
Schedule()
返回任务,您只需要使用它

其次,为了避开泛型问题,您可以将其全部隐藏在(非泛型)
Action
s中。在
Schedule()
中,使用lambda创建一个操作,该操作完全满足您的需要。然后,消费循环将执行该操作,它不需要知道内部是什么

第三,我不明白为什么在循环的每次迭代中都要开始一个新的
任务。首先,这意味着你实际上不会得到任何节流

通过这些修改,代码可能如下所示:

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
    }
}
公共类请求调度器
{
private readonly BlockingCollection m_queue=新建BlockingCollection();
公共请求调度程序()
{
这个。Start();
}
私有void Start()
{
Task.Factory.StartNew(()=>
{
foreach(m_queue.GetConsumingEnumerable()中的var操作)
{
动作();
}
},TaskCreationOptions.LongRunning);
}
公共任务计划(IOOperation操作)
{
var tcs=new TaskCompletionSource();
动作动作=()=>
{
尝试
{
tcs.SetResult(ProcessItem(操作));
}
捕获(例外e)
{
tcs.SetException(e);
}
};
m_queue.Add(操作);
返回tcs.Task;
}
私有T ProcessItem(IOOperation操作)
{
//随便
}
}

是否有任何方法可以传递回调操作,并使用lambda在具有所有可用类型的位置创建该操作?这是什么意思?动作委托不接受参数,如何返回数据?为什么调度程序调用回调?是否在
ProcessItem
的定义中不调用回调?是的,我同意,上面的代码不起作用,只是一个草图,所以它当然可以在ProcessItem中调用。这将如何解决键入问题?@MalcomTucker,因为
RequestScheduler
不需要知道结果的类型,因为它不调用回调。在
ProcessItem
的定义中,您当然知道正确调用回调所需的结果类型(并且实际有结果)。非常感谢,我昨晚发现了TaskCompletionSource,并刚刚完成了一些工作,所以我很高兴我的思路是正确的。节流发生在BlockingCollection上的
foreach
中,为了清楚起见,我省略了它。最后一个问题-为什么要使用
LongRunning
选项?我的API调用大约为70毫秒-是否足够长的运行时间来使用此选项?@MalcomTucker每次API调用需要多长时间并不重要。重要的是整个
任务需要多长时间。在我看来,这可能是一段很长的时间,其中大部分时间可能会花在等待
getConsumineumerable()
返回另一项上。明白了,好的,是的,特定任务只是无限期地运行,将消息传输到API,只有在用户断开连接时才会关闭。。谢谢你的帮助,聪明的家伙(像往常一样!)