C# 并行处理许多HTTP web请求的好方法是什么?

C# 并行处理许多HTTP web请求的好方法是什么?,c#,multithreading,.net-4.0,C#,Multithreading,.net 4.0,我正在构建一个通用的URI检索系统。本质上,有一个泛型类Retriever,它维护一个要检索的URI队列。它有一个单独的线程,可以尽可能快地处理该队列。问题标题中指出的URI类型的一个示例是HTTP类型URI 问题是,当我开始请求通过抽象方法T RetrieveResource(Uri位置)检索资源时,由于缺乏异步性,它的速度会减慢 我的第一个想法是将RetrieveResource的返回类型更改为Task。然而,当我们有数千个未完成的任务时,这似乎会使任务堆积起来并导致许多问题。它似乎创建了许

我正在构建一个通用的URI检索系统。本质上,有一个泛型类
Retriever
,它维护一个要检索的URI队列。它有一个单独的线程,可以尽可能快地处理该队列。问题标题中指出的URI类型的一个示例是HTTP类型URI

问题是,当我开始请求通过抽象方法
T RetrieveResource(Uri位置)
检索资源时,由于缺乏异步性,它的速度会减慢

我的第一个想法是将
RetrieveResource
的返回类型更改为
Task
。然而,当我们有数千个未完成的任务时,这似乎会使任务堆积起来并导致许多问题。它似乎创建了许多实际的线程,而不是使用线程池。我想这会让一切都慢下来,因为一次发生的事情太多了,所以没有任何事情能取得重大进展

预计我们将有大量排队项目要检索,并且无法像排队时那样快速处理这些项目。随着时间的推移,系统有机会迎头赶上;但这肯定不快

我也考虑过不用维护队列和线程来处理它。。。仅在
线程池
上将工作项排队。但是,如果说我需要在处理所有工作项之前关闭系统,或者以后需要考虑优先级或其他事情,我不确定这是否理想

我们还知道,检索资源是一个耗时的过程(0.250-5秒),但不一定是一个资源密集型过程。我们很好地将此并行化为数百个请求

我们的要求是:

  • URI可以从任何线程排队,即使系统正在处理队列时也是如此
  • 以后可能需要对检索进行优先级排序
  • 检索应该能够暂停
  • 当没有检索到任何内容时,应该发生最小的旋转(
    BlockingCollection
    在这里很有用)
有没有一种好的方法可以在不引入不必要的复杂性的情况下并行化它

下面是我们现有的一些代码,作为示例

public abstract class Retriever<T> : IRetriever<T>, IDisposable
{
    private readonly Thread worker;
    private readonly BlockingCollection<Uri> pending;
    private volatile int isStarted;
    private volatile int isDisposing;

    public event EventHandler<RetrievalEventArgs<T>> Retrieved;

    protected Retriever()
    {
        this.worker = new Thread(this.RetrieveResources);
        this.pending = new BlockingCollection<Uri>(new ConcurrentQueue<Uri>());
        this.isStarted = 0;
        this.isDisposing = 0;
    }

    ~Retriever()
    {
        this.Dispose(false);
    }

    private void RetrieveResources()
    {
        while (this.isDisposing == 0)
        {
            while (this.isStarted == 0)
            {
                Monitor.Wait(this.pending);
            }

            Uri location = this.pending.Take();

            // This is what needs to be concurrently done.
            // In this example, it's synchronous, but just on a separate thread.
            T result = this.RetrieveResource(location);

            // At this point, we would fire our event with the retrieved data
        }
    }

    protected abstract T RetrieveResource(Uri location);

    protected void Dispose(bool disposing)
    {
        if (Interlocked.CompareExchange(ref this.isDisposing, 1, 0) == 1)
        {
            return;
        }

        if (disposing)
        {
            this.pending.CompleteAdding();
            this.worker.Join();
        }
    }

    public void Add(Uri uri)
    {
        try
        {
            this.pending.Add(uri);
        }
        catch (InvalidOperationException)
        {
            return;
        }
    }

    public void AddRange(IEnumerable<Uri> uris)
    {
        foreach (Uri uri in uris)
        {
            try
            {
                this.pending.Add(uri);
            }
            catch (InvalidOperationException)
            {
                return;
            }
        }
    }

    public void Start()
    {
        if (Interlocked.CompareExchange(ref this.isStarted, 1, 0) == 1)
        {
            throw new InvalidOperationException("The retriever is already started.");
        }

        if (this.worker.ThreadState == ThreadState.Unstarted)
        {
            this.worker.Start();
        }

        Monitor.Pulse(this.pending);
    }

    public void Stop()
    {
        if (Interlocked.CompareExchange(ref this.isStarted, 0, 1) == 0)
        {
            throw new InvalidOperationException("The retriever is already stopped.");
        }
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
}
公共抽象类检索器:IRetriever,IDisposable
{
私有只读线程工作者;
私有只读阻止收集挂起;
私有化开始;
私有易失性int isDisposing;
检索到公共事件事件处理程序;
受保护的检索器()
{
this.worker=新线程(this.RetrieveResources);
this.pending=new BlockingCollection(new ConcurrentQueue());
this.isStarted=0;
此.isDisposing=0;
}
~Retriever()
{
本.处置(假);
}
私有无效检索资源()
{
while(this.isDisposing==0)
{
while(this.isStarted==0)
{
Monitor.Wait(this.pending);
}
Uri location=this.pending.Take();
//这是需要同时做的事情。
//在本例中,它是同步的,但只是在一个单独的线程上。
T结果=此.RetrieveResource(位置);
//此时,我们将使用检索到的数据触发事件
}
}
受保护的抽象T检索资源(Uri位置);
受保护的无效处置(bool处置)
{
if(联锁比较交换(参考此isDisposing,1,0)==1)
{
返回;
}
如果(处置)
{
this.pending.CompleteAdding();
this.worker.Join();
}
}
公共无效添加(Uri)
{
尝试
{
this.pending.Add(uri);
}
捕获(无效操作异常)
{
返回;
}
}
公共void AddRange(IEnumerable URI)
{
foreach(Uri中的Uri)
{
尝试
{
this.pending.Add(uri);
}
捕获(无效操作异常)
{
返回;
}
}
}
公开作废开始()
{
if(联锁比较交换(参考this.isStart,1,0)==1)
{
抛出新的InvalidOperationException(“检索器已启动”);
}
if(this.worker.ThreadState==ThreadState.Unstarted)
{
this.worker.Start();
}
监视器。脉冲(此。待定);
}
公共停车场()
{
if(联锁比较交换(参考this.isStarted,0,1)=0)
{
抛出新的InvalidOperationException(“检索器已停止”);
}
}
公共空间处置()
{
这个。处置(真实);
总干事(本);
}
}
以上面的例子为基础。。。我认为这一解决方案增加了太多的复杂性,或者更确切地说,是奇怪的代码。。。就是这个

    private void RetrieveResources()
    {
        while (this.isDisposing == 0)
        {
            while (this.isStarted == 0)
            {
                Monitor.Wait(this.pending);
            }

            Uri location = this.pending.Take();

            Task<T> task = new Task<T>((state) =>
                {
                    return this.RetrieveResource(state as Uri);
                }, location);

            task.ContinueWith((t) =>
                {
                    T result = t.Result;
                    RetrievalEventArgs<T> args = new RetrievalEventArgs<T>(location, result);

                    EventHandler<RetrievalEventArgs<T>> callback = this.Retrieved;
                    if (!Object.ReferenceEquals(callback, null))
                    {
                        callback(this, args);
                    }
                });

            task.Start();
        }
    }
private void RetrieveResources()
{
while(this.isDisposing==0)
{
while(this.isStarted==0)
{
Monitor.Wait(this.pending);
}
Uri location=this.pending.Take();
任务任务=新任务((状态)=>
{
返回此.RetrieveResource(状态为Uri);
},地点);
任务。继续((t)=>
{
T结果=T.结果;
RetrievalEventArgs args=新的RetrievalEventArgs(位置、结果);
EventHandler callback=this.received;
如果(!Object.ReferenceEquals(回调,null))
{
回调(this,args);
}
});
task.Start();
}
}

我想我已经想出了一个很好的解决方案。我提取了两种方法
public abstract class Retriever<T> : IRetriever<T>
{
    private readonly object locker;
    private readonly BlockingCollection<Uri> pending;
    private readonly Thread[] threads;
    private CancellationTokenSource cancellation;

    private volatile int isStarted;
    private volatile int isDisposing;

    public event EventHandler<RetrieverEventArgs<T>> Retrieved;

    protected Retriever(int concurrency)
    {
        if (concurrency <= 0)
        {
            throw new ArgumentOutOfRangeException("concurrency", "The specified concurrency level must be greater than zero.");
        }

        this.locker = new object();
        this.pending = new BlockingCollection<Uri>(new ConcurrentQueue<Uri>());
        this.threads = new Thread[concurrency];
        this.cancellation = new CancellationTokenSource();

        this.isStarted = 0;
        this.isDisposing = 0;

        this.InitializeThreads();
    }

    ~Retriever()
    {
        this.Dispose(false);
    }

    private void InitializeThreads()
    {
        for (int i = 0; i < this.threads.Length; i++)
        {
            Thread thread = new Thread(this.ProcessQueue)
            {
                IsBackground = true
            };

            this.threads[i] = thread;
        }
    }

    private void StartThreads()
    {
        foreach (Thread thread in this.threads)
        {
            if (thread.ThreadState == ThreadState.Unstarted)
            {
                thread.Start();
            }
        }
    }

    private void CancelOperations(bool reset)
    {
        this.cancellation.Cancel();
        this.cancellation.Dispose();

        if (reset)
        {
            this.cancellation = new CancellationTokenSource();
        }
    }

    private void WaitForThreadsToExit()
    {
        foreach (Thread thread in this.threads)
        {
            thread.Join();
        }
    }

    private void ProcessQueue()
    {
        while (this.isDisposing == 0)
        {
            while (this.isStarted == 0)
            {
                Monitor.Wait(this.locker);
            }

            Uri location;

            try
            {
                location = this.pending.Take(this.cancellation.Token);
            }
            catch (OperationCanceledException)
            {
                continue;
            }

            T data;

            try
            {
                data = this.Retrieve(location, this.cancellation.Token);
            }
            catch (OperationCanceledException)
            {
                continue;
            }

            RetrieverEventArgs<T> args = new RetrieverEventArgs<T>(location, data);

            EventHandler<RetrieverEventArgs<T>> callback = this.Retrieved;
            if (!Object.ReferenceEquals(callback, null))
            {
                callback(this, args);
            }
        }
    }

    private void ThowIfDisposed()
    {
        if (this.isDisposing == 1)
        {
            throw new ObjectDisposedException("Retriever");
        }
    }

    protected abstract T Retrieve(Uri location, CancellationToken token);

    protected virtual void Dispose(bool disposing)
    {
        if (Interlocked.CompareExchange(ref this.isDisposing, 1, 0) == 1)
        {
            return;
        }

        if (disposing)
        {
            this.CancelOperations(false);
            this.WaitForThreadsToExit();
            this.pending.Dispose();
        }
    }

    public void Start()
    {
        this.ThowIfDisposed();

        if (Interlocked.CompareExchange(ref this.isStarted, 1, 0) == 1)
        {
            throw new InvalidOperationException("The retriever is already started.");
        }

        Monitor.PulseAll(this.locker);
        this.StartThreads();
    }

    public void Add(Uri location)
    {
        this.pending.Add(location);
    }

    public void Stop()
    {
        this.ThowIfDisposed();

        if (Interlocked.CompareExchange(ref this.isStarted, 0, 1) == 0)
        {
            throw new InvalidOperationException("The retriever is already stopped.");
        }

        this.CancelOperations(true);
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
}