代码之家  ›  专栏  ›  技术社区  ›  Kevin Hakanson

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

  •  5
  • Kevin Hakanson  · 技术社区  · 15 年前

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

    在研究这一点时,我可以考虑以下几种选择:

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

    我需要找到一个既能发挥作用又能按比例执行的解决方案。这就是我担心线程池大小的原因。我不想让请求阻塞,因为它们正在等待可用的线程。

    4 回复  |  直到 14 年前
        1
  •  1
  •   Noah Heldman    15 年前

    我更喜欢手动执行这类操作,而不是依赖异步Web请求或线程池的自动调整大小(默认为25个线程)。当然,这些都是很好的解决问题的方法,但是我认为下面的代码更易读一些(在下面的示例中,“链接”将包含处理前的链接列表…):

    private static IList<String> _links = new List<String>();
    private const int NumberOfThreads = 2;
    
    public void SpawnWebRequests()
    {
        IList<Thread> threadList = new List<Thread>();
    
        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;
        }
    }
    

    如果只想管理线程池的大小(如果使用的是thread pool.queueUserWorkItem()),则可以使用thread pool.setMaxThreads=2。

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

    希望能有所帮助, 诺亚

        2
  •  1
  •   Kevin Hakanson    14 年前

    答案之一 Multithreading WebRequests, a good and stable approach? : CSharp 使用A ManualResetEvent event = new ManualResetEvent() ,一个等于飞行中请求数量的参考计数器,以及 Interlocked.Decrement 控制 event.Set() . 然后,主线程通过调用 event.WaitOne() .

    然而, WaitHandles - Auto/ManualResetEvent and Mutex 提到ManualResetEvent“比使用各种监视方法(如Wait、Pulse和Pulseall)慢得多。

    我最终把我的代码建立在这个Noah Blumenthal的博客上: Run generic tasks async (fluent-ly) .我做了两个更改:实现 IDisposable 并打电话 .Close() ManualResetEvent 从使用 lock() Interlocked .Increment() .Decrement() .

    public class AsyncQueueManager : IDisposable {
        private readonly ManualResetEvent waitHandle = new ManualResetEvent(true);
        private int count = 0;
    
        public AsyncQueueManager Queue(Action<object> action) {
            Interlocked.Increment(ref count);
            waitHandle.Reset();
            Action<object> actionWrapper = CreateActionWrapper(action);
            WaitCallback waitCallback = new WaitCallback(actionWrapper);
            ThreadPool.QueueUserWorkItem(waitCallback);
            return this;
        }
    
        private Action<object> CreateActionWrapper(Action<object> action) {
            Action<object> 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();
        }
    }
    
        3
  •  0
  •   Ed Power    15 年前

    我建议您创建一个执行httpwebrequest的worker类,并在它自己的线程中为每个连接启动它。您可以加入线程,等待它们都完成,或者传递回调方法。无论哪种情况,您都需要考虑连接失败、超时和其他异常。我更喜欢使用一个回调来返回连接结果和线程的managedThreadID,这是我用来跟踪线程的。辅助类应捕获所有异常,以便在调用类中处理它们。

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

        4
  •  -2
  •   GrayWizardx    15 年前

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

    也许最简单的实现方法是 不同的回调函数,其中一个用于service1,另一个用于service2,在每个completionEvents中将某些触发器变量设置为“true”,然后在主线程中等待同时设置这两个触发器变量。您可以使用ReseteEvents来完成这项工作,但是如果您的服务器将处于加载状态,您可能希望避免这种情况。

    过程的伪代码可能是:

    Dictionary<string, bool> callCompleted = new Dictionary<string, bool>
    
    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;
    }
    

    这有点困难,因为我不知道你打电话的确切性质,但它应该工作得相当好。