Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
534 views
in Technique[技术] by (71.8m points)

c# - Shouldn't lesser number of threads be used if I use async?

My understanding is if I use async, the thread makes the web request and moves on. When the response comes back another thread picks it up from there. So there are a lesser number of tied up threads sitting idle. Wouldn't this mean the maximum number of live threads would go down? But in the example below, the code that doesn't use async ends up using lesser number of threads. Can some one explain why?

Code without async (uses lesser threads):

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;

namespace NoAsync
{
    internal class Program
    {
        private const int totalCalls = 100;

        private static void Main(string[] args)
        {
            for (int i = 1; i <= totalCalls; i++)
            {
                ThreadPool.QueueUserWorkItem(GoogleSearch, i);
            }

            Thread.Sleep(100000);
        }

        private static void GoogleSearch(object searchTerm)
        {
            Thread.CurrentThread.IsBackground = false;
            string url = @"https://www.google.com/search?q=" + searchTerm;
            Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
            WebRequest wr = WebRequest.Create(url);
            var httpWebResponse = (HttpWebResponse) wr.GetResponse();
            var reader = new StreamReader(httpWebResponse.GetResponseStream());
            string responseFromServer = reader.ReadToEnd();
            //Console.WriteLine(responseFromServer); // Display the content.
            httpWebResponse.Close();
            Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
        }
    }
}

Code with async (uses more threads)

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace AsyncAwait
{
    internal class Program
    {
        private const int totalCalls = 100;
        private static DateTime start = System.DateTime.Now;

    private static void Main(string[] args)
    {
        var tasks = new List<Task>();

        for (int i = 1; i <= totalCalls; i++)
        {
            var searchTerm = i;
            var t = GoogleSearch(searchTerm);
            tasks.Add(t);
        }

        Task.WaitAll(tasks.ToArray());
        Console.WriteLine("Hit Enter to exit");
        Console.ReadLine();
    }

    private static async Task GoogleSearch(object searchTerm)
    {
        Thread.CurrentThread.IsBackground = false;
        string url = @"https://www.google.com/search?q=" + searchTerm;
        Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
        using (var client = new HttpClient())
        {
            using (HttpResponseMessage response = await client.GetAsync(url))
            {
                HttpContent content = response.Content;
                content.Dispose();
                Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
                Console.WriteLine("TimeSpan consumed {0}", System.DateTime.Now.Subtract(start));
            }
        }
    }
}

}

I do understand my results include unmanaged threads. But shouldn't the total number of threads still be lower?

Update: I updated the async call with the code provided by Noseratio

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

ThreadPool actually maintains two sub-pools, one for worker threads, and another for IOCP threads. When the result of GetAsync is available, a random IOCP thread (I/O Completion Port) gets allocated from the pool to handle the completion of the async HTTP request. That's where the code after await gets executed. You have control over the size of each sub-pool with ThreadPool.SetMinThreads/SetMaxThreads, more about this below.

As is, your non-async code can hardly be compared to the async version. For a more fair comparison, you should stick to WebRequest for both cases, e.g.:

Non-Async:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace NoAsync
{
    internal class Program
    {
        private const int totalCalls = 100;

        private static void Main(string[] args)
        {
            int maxWorkers, maxIOCPs;
            ThreadPool.GetMaxThreads(out maxWorkers, out maxIOCPs);
            int minWorkers, minIOCPs;
            ThreadPool.GetMinThreads(out minWorkers, out minIOCPs);
            Console.WriteLine(new { maxWorkers, maxIOCPs, minWorkers, minIOCPs });

            ThreadPool.SetMinThreads(100, 100);

            var tasks = new List<Task>();

            for (int i = 1; i <= totalCalls; i++)
                tasks.Add(Task.Run(() => GoogleSearch(i)));

            Task.WaitAll(tasks.ToArray());
        }

        private static void GoogleSearch(object searchTerm)
        {
            string url = @"https://www.google.com/search?q=" + searchTerm;
            Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
            WebRequest wr = WebRequest.Create(url);
            var httpWebResponse = (HttpWebResponse)wr.GetResponse();
            var reader = new StreamReader(httpWebResponse.GetResponseStream());
            string responseFromServer = reader.ReadToEnd();
            //Console.WriteLine(responseFromServer); // Display the content.
            reader.Close();
            httpWebResponse.Close();
            Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
        }
    }
}

Async:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace Async
{
    internal class Program
    {
        private const int totalCalls = 100;

        private static void Main(string[] args)
        {
            int maxWorkers, maxIOCPs;
            ThreadPool.GetMaxThreads(out maxWorkers, out maxIOCPs);
            int minWorkers, minIOCPs;
            ThreadPool.GetMinThreads(out minWorkers, out minIOCPs);
            Console.WriteLine(new { maxWorkers, maxIOCPs, minWorkers, minIOCPs });

            ThreadPool.SetMinThreads(100, 100);

            var tasks = new List<Task>();

            for (int i = 1; i <= totalCalls; i++)
                tasks.Add(GoogleSearch(i));

            Task.WaitAll(tasks.ToArray());
        }

        private static async Task GoogleSearch(object searchTerm)
        {
            string url = @"https://www.google.com/search?q=" + searchTerm;
            Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
            WebRequest wr = WebRequest.Create(url);
            var httpWebResponse = (HttpWebResponse) await wr.GetResponseAsync();
            var reader = new StreamReader(httpWebResponse.GetResponseStream());
            string responseFromServer = await reader.ReadToEndAsync();
            //Console.WriteLine(responseFromServer); // Display the content.
            reader.Close();
            httpWebResponse.Close();
            Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
        }
    }
}

By default, I see the following figures for the thread pool on my system:

{ maxWorkers = 32767, maxIOCPs = 1000, minWorkers = 4, minIOCPs = 4 }

Thread pool is lazy when growing threads. A new thread creation can be delayed for up to 500ms (for more details, check Joe Duffy's "CLR thread pool injection, stuttering problems").

To account for this behavior, use ThreadPool.SetMinThreads. With SetMinThreads(100, 100), I see ~111 threads at peak for the sync version, and ~20 threads at peak for the async version (Release build, running without debugger). This is quite an indicative difference, on behalf of the async version.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...