在ASP.NET中并行执行.NET HttpWebRequests的建议

我有一个ASP.NET MVC Web应用程序,可以对其他服务器进行REST样式的Web服务调用。 我有一个场景,我正在对两个单独的服务进行两次HttpWebRequest调用。 我需要他们两个完成继续,但他们的顺序无关紧要。 它们可能每个需要1-2秒,我现在按顺序运行它们。 并行运行会减少用户响应时间,但最好的方法是什么?

在研究这个时,我可以想到几个选择:

  • 在主线程上执行一个请求,并为另一个请求启动第二个线程。 应该创建新线程还是使用线程池? 如果我使用游泳池,我该如何调整尺寸? 另外,不知道如何将线程重新连接在一起(例如使用ThreadPool.RegisterWaitForSingleObject )?
  • 尝试并利用内置的IAsyncResult支持一个或两个请求。 同样,不确定异步请求在哪些线程上执行,因此不确定如何调整线程池的大小。 如何将IAsyncResult加入我的主线程? 所有的例子我都在回调中找到了进程信息,但是我可以在主线程中等待并使用IsCompleted属性?

我需要找到一个既能运作又能大规模运行的解决方案。 这就是我担心线程池大小的原因。 我不愿意阻止请求,因为他们正在等待可用的线程。

我喜欢手动执行此类操作,而不是依赖异步Web请求或线程池的自动resize(默认情况下为25个线程)。 当然,这些是解决问题的完美方法,但我认为以下代码更具可读性(在下面的示例中,_links将在处理之前包含链接列表…):

private static IList _links = new List(); private const int NumberOfThreads = 2; public void SpawnWebRequests() { IList threadList = new List(); for (int i = 0; i < NumberOfThreads; i++) { var thread = new Thread(ProcessWebRequests); threadList.Add(thread); thread.Start(); } for (int i = 0; i < NumberOfThreads; i++) { threadList[i].Join(); } } private static void ProcessWebRequests() { String link; while (true) { lock(_links) { if (_links.Count == 0) break; link = _links.RemoveAt(0); } ProcessWebRequest(link); } } private static void ProcessWebRequest(String link) { try { var request = (HttpWebRequest)WebRequest.Create(link); request.Method = "HEAD"; // or "GET", since some sites (Amazon) don't allow HEAD request.Timeout = DefaultTimeoutSeconds * 1000; // Get the response (throws an exception if status != 200) using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK) Log.Debug("Working link: {0}", request.RequestUri); } } catch (WebException ex) { var response = ((HttpWebResponse)ex.Response); var status = response != null ? response.StatusCode : HttpStatusCode.RequestTimeout; Log.WarnException(String.Format("Broken link ({0}): {1}", status, link), ex); // Don't rethrow, as this is an expected exception in many cases } catch (Exception ex) { Log.ErrorException(String.Format("Error processing link {0}", link), ex); // Rethrow, something went wrong throw; } } 

如果您只想管理线程池的大小(如果您使用的是ThreadPool.QueueUserWorkItem()),则可以使用ThreadPool.SetMaxThreads = 2)。

当然,如果您想使用Microsoft认可的异步方法,请查看此示例: http : //msdn.microsoft.com/en-us/library/86wf6409.aspx 。 只需确保清理每个响应(通过“使用”块或关闭响应对象)!

希望有所帮助,诺亚

multithreadingWebRequests的一个答案,一个好的和稳定的方法? :CSharp使用ManualResetEvent event = new ManualResetEvent() ,一个等于飞行请求数的引用计数器和Interlocked.Decrement来控制event.Set() 。 然后主线程通过调用event.WaitOne()等待。

但是, WaitHandles – Auto / ManualResetEvent和Mutex提到,ManualResetEvent“可能比使用Wait,Pulse和PulseAll等各种Monitor方法慢得多。

我最终将我的代码从Noah Blumenthal博客文章中删除: 运行通用任务async(流利的) 。 我做了两个更改:实现IDisposable并在ManualResetEvent上调用.Close()并从使用lock()切换到Interlocked .Decrement().Decrement()

 public class AsyncQueueManager : IDisposable { private readonly ManualResetEvent waitHandle = new ManualResetEvent(true); private int count = 0; public AsyncQueueManager Queue(Action action) { Interlocked.Increment(ref count); waitHandle.Reset(); Action actionWrapper = CreateActionWrapper(action); WaitCallback waitCallback = new WaitCallback(actionWrapper); ThreadPool.QueueUserWorkItem(waitCallback); return this; } private Action CreateActionWrapper(Action action) { Action actionWrapper = (object state) => { try { action(state); } catch (Exception ex) { // log } finally { if (Interlocked.Decrement(ref count) == 0) { waitHandle.Set(); } } }; return actionWrapper; } public void Wait() { waitHandle.WaitOne(); } public void Wait(TimeSpan timeout) { waitHandle.WaitOne(timeout); } public void Dispose() { waitHandle.Close(); } } 

我建议您创建一个执行HttpWebRequest的工作类,并在每个连接的自己的线程中启动它。 你可以加入线程并等到它们都完成,或者传递一个回调方法。 在任何一种情况下,您都需要考虑连接失败,超时和其他exception。 我更喜欢使用一个返回连接结果的回调和线程的ManagedThreadId,我用它来跟踪线程。 worker类应该捕获所有exception,以便您可以在调用类中处理它们。

本文提供了一些有关何时超过最大连接数的见解和修复: http : //support.microsoft.com/kb/821268 。

除非您正在做一些花哨的事情,否则ThreadPool的大小是固定的,并且通常足够大以满足您的需求。 在您的特定情况下,如果您的Web服务负载很重并且每个调用者已为其自己的回调启动了2个TP线程,则可能会遇到使用AsyncIO调用的资源约束问题。

也许最简单的实现可能是拥有两个不同的回调函数,一个用于service1,一个用于每个CompletionEvents中的service2,将一些触发变量设置为“true”,然后在主线程中等待两个触发变量的设置。 您可以使用ResetEvents执行此操作,但如果您的服务器处于负载状态,您可能希望避免这种情况。

该过程的伪代码可能是:

 Dictionary callCompleted = new Dictionary string operation1Key = Guid.NewGuid().ToString(); string operation2Key = Guid.NewGuid().ToString(); callCompleted[operation1Key] = false; callCompleted[operation2Key] = false; //make your remote calls and pass the operation key as the data //.... //.... bool waiting = true; while (waiting) { bool isFinished = true; foreach(string operationKey in callCompleted.Keys) { isFinished &= callCompleted[operationKey] if (!isFinished) { break; } } waiting = !isFinished; } 

由于我不知道你如何打电话的确切性质,它有点激动,但它应该运作得相当好。