同时运行x个web请求
本文关键字:web 请求 运行 | 更新日期: 2023-09-27 18:12:07
我们公司有一个web服务,我想通过我自己的c# HTTPWebRequest
客户端发送XML文件(存储在我的驱动器上)。这已经起作用了。web服务同时支持5个同步请求(一旦服务器上的处理完成,我就会从web服务获得响应)。处理每个请求大约需要5分钟。
抛出太多请求(> 5)会导致我的客户端超时。此外,这可能导致服务器端出现错误和数据不一致。在服务器端进行更改不是一个选项(来自不同的供应商)。
现在,我的Webrequest
客户端将发送XML并等待使用result.AsyncWaitHandle.WaitOne();
的响应
然而,通过这种方式,一次只能处理一个请求,尽管web服务支持5个。我尝试使用Backgroundworker
和Threadpool
,但它们同时创建了太多的请求,这使它们对我无用。有什么建议,如何解决这个问题?创建我自己的Threadpool
正好有5个线程?有什么建议,如何实现这一点?
最简单的方法是创建5个线程(注意:这是一个奇数!)
使用BlockingCollection
的xml文件。 类似:
var bc = new BlockingCollection<string>();
for ( int i = 0 ; i < 5 ; i++ )
{
new Thread( () =>
{
foreach ( var xml in bc.GetConsumingEnumerable() )
{
// do work
}
}
).Start();
}
bc.Add( xml_1 );
bc.Add( xml_2 );
...
bc.CompleteAdding(); // threads will end when queue is exhausted
如果你使用的是。net 4,这看起来非常适合Parallel.ForEach()
。您可以设置它的MaxDegreeOfParallelism
,这意味着您可以保证一次不会处理更多的项。
Parallel.ForEach(items,
new ParallelOptions { MaxDegreeOfParallelism = 5 },
ProcessItem);
这里,ProcessItem
是一个方法,它通过访问服务器并阻塞来处理一个项目,直到处理完成。如果需要,可以使用lambda。
创建自己的五个线程的线程池并不困难—只需创建一个描述要发出的请求的对象的并发队列,并有五个线程根据需要循环执行任务。添加一个AutoResetEvent,你可以确保它们在没有需要处理的请求时不会疯狂旋转。
将响应返回给正确的调用线程是很棘手的。如果这是您的其余代码工作方式的情况,我会采取不同的方法,并创建一个类似监视器的限制器,但允许5个线程同时运行,而不是只有一个:
private static class RequestLimiter
{
private static AutoResetEvent _are = new AutoResetEvent(false);
private static int _reqCnt = 0;
public ResponseObject DoRequest(RequestObject req)
{
for(;;)
{
if(Interlocked.Increment(ref _reqCnt) <= 5)
{
//code to create response object "resp".
Interlocked.Decrement(ref _reqCnt);
_are.Set();
return resp;
}
else
{
if(Interlocked.Decrement(ref _reqCnt) >= 5)//test so we don't end up waiting due to race on decrementing from finished thread.
_are.WaitOne();
}
}
}
}
您可以编写一个小助手方法,它将阻塞当前线程,直到所有线程都完成执行给定的操作委托。
static void SpawnThreads(int count, Action action)
{
var countdown = new CountdownEvent(count);
for (int i = 0; i < count; i++)
{
new Thread(() =>
{
action();
countdown.Signal();
}).Start();
}
countdown.Wait();
}
然后使用BlockingCollection<string>
(线程安全集合)来跟踪您的xml文件。通过使用上面的helper方法,您可以编写如下内容:
static void Main(string[] args)
{
var xmlFiles = new BlockingCollection<string>();
// Add some xml files....
SpawnThreads(5, () =>
{
using (var web = new WebClient())
{
web.UploadFile(xmlFiles.Take());
}
});
Console.WriteLine("Done");
Console.ReadKey();
}
一个更好的方法是异步上传文件,这样你就不会浪费资源在为IO任务使用线程上。
你可以再写一个辅助方法:
static void SpawnAsyncs(int count, Action<CountdownEvent> action)
{
var countdown = new CountdownEvent(count);
for (int i = 0; i < count; i++)
{
action(countdown);
}
countdown.Wait();
}
并像这样使用:
static void Main(string[] args)
{
var urlXML = new BlockingCollection<Tuple<string, string>>();
urlXML.Add(Tuple.Create("http://someurl.com", "filename"));
// Add some more to collection...
SpawnAsyncs(5, c =>
{
using (var web = new WebClient())
{
var current = urlXML.Take();
web.UploadFileCompleted += (s, e) =>
{
// some code to mess with e.Result (response)
c.Signal();
};
web.UploadFileAsyncAsync(new Uri(current.Item1), current.Item2);
}
});
Console.WriteLine("Done");
Console.ReadKey();
}