Best Practices for Running .NET HttpWebRequests in parallel in ASP.NET

I have an ASP.NET MVC web application that makes REST-style web service requests to other servers. I have a scenario when I make two HttpWebRequest calls to two separate services. I need them both to finish in order to continue, but their order doesn't matter. They can take 1-2 seconds each, and I'm running them in sequence now. Running them in parallel would decrease user response time, but what is the best way?

Studying this, I can present several options:

  • Run one request in the main thread and allocate the second thread for another request. Should new threads be created or use a thread pool? If I use a pool, how do I size it? Also, not sure how I can join threads together (like using ThreadPool.RegisterWaitForSingleObject )?
  • Try using the built-in IAsyncResult support for one or both of the queries. Again, I’m not sure which threads are performing the asynchronous request, so I’m not sure how to determine the size of the thread pool. How to join IAsyncResult in my main thread? In all the examples, I find the process information in the callback, but can I just wait in my main thread and use the IsCompleted property ?

I need to find a solution that will function and work on a scale. That's why I worry about the size of the pool threads. I would not want to block requests because they are waiting for available threads.

+5
source share
4 answers

One answer WebRequests multithreading is a good and stable approach ?: CSharp uses a ManualResetEvent event = new ManualResetEvent()control counter equal to the number of requests for flight and Interlocked.Decrementfor management event.Set(). Then the main thread waits, calling event.WaitOne().

WaitHandles - Auto/ManualResetEvent Mutex , ManualResetEvent " , Monitor", Wait, .

, Noah Blumenthal: async (). : 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();
    }
}
+1

, , - ( 25 ). , , , ( _links , ...):

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;
    }
}

( ThreadPool.QueueUserWorkItem()), ThreadPool.SetMaxThreads = 2).

, Microsoft , : http://msdn.microsoft.com/en-us/library/86wf6409.aspx. , ( "using" )!

, ,

+1

, HttpWebRequest, . , , . , . , ManagedThreadId, . , .

, : http://support.microsoft.com/kb/821268.

0

- , ThreadPool , . , AsyncIO, - , 2 TP .

, , service1 service2 CompletionEvents "true", , . ResetEvents, , .

The process pseudo-code can be:

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;
}

A little rude, because I don’t know the exact nature of how you make your calls, but it should work quite well.

-2
source

All Articles