同时运行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个。我尝试使用BackgroundworkerThreadpool,但它们同时创建了太多的请求,这使它们对我无用。有什么建议,如何解决这个问题?创建我自己的Threadpool正好有5个线程?有什么建议,如何实现这一点?

同时运行x个web请求

最简单的方法是创建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();
}