0

I have an app (http web load test app) that need new Thread() , and the HttpClient only have async method, so how do I run the action synchronous

ps: I tried use full Task but the thread number it use is low (30 thread only), so I want to try the Thread to see if it can be much faster. Will the .GetAwaiter().GetResult() cost 2 thread (100 thread became 200 thread) ?


previous I use

for(var i = 0; i< 200;i++)
{
    Task.Run(async ()=>
    {
        while(thereStillHaveRequestToMake)
        {
        await httpclient.SendAsync() // some thing like this
        }
    });
}

// the prolem is there are only 30-40 Thread in use (From TaskManager)

So I want to switch to use Thread directly

for(var i = 0; i< 200;i++)
{
    new Thread(()=>
    {
        while(thereStillHaveRequestToMake)
        {
            httpclient.SendAsync().GetAwaiter.GetResult()
        }
    });
}
John
  • 716
  • 1
  • 7
  • 24
  • 5
    Please read [Stephen Cleary: There is no Thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) – Sir Rufo Jan 22 '19 at 06:23
  • 2
    You assumptions are wrong, `.GetAwaiter().GetResult()` doesn't cost any threads async io calls wont use any threads if written correctly. If you a writing a load tester, i suggest you use something like DataFlow, which will allow you better fine grain control over threads and also will work well with async. however its all hard to tell as we dont see the code you are trying to do this with.. Although this isnt a classic definition of an XY problem, it is XY in the fact you want to do the wrong things for the wrong reasons – TheGeneral Jan 22 '19 at 06:23
  • @TheGeneral I read this https://stackoverflow.com/questions/34151179/async-await-deadlocking-when-using-a-synchronizationcontext today, I can not understand the "`.GetAwaiter().GetResult()` doesn't cost any threads async io calls wont use any threads if written correctly.", I think it will use additional thread to complete the action that after the `await` keyword ,because the current thread is blocked until the whole Task complete unless the Task is the first true async Task (not use the `async,await` keyword) – John Jan 23 '19 at 08:21
  • The reason this question is not possible to answer is that you have not given a reason nor an example as to why *this code has to run synchronous*. Otherwise it's just a duplicate of [How to call asynchronous method from synchronous method in C#?](https://stackoverflow.com/questions/9343594). – Erik Philips Jan 23 '19 at 08:50
  • If the `.SendAsync()` method explicitly creates a new thread then there is no way to force it to run on your desired thread. – Enigmativity Jan 23 '19 at 08:51
  • You seem to be missing one key point. `await` doesn't *start* anything running or control the threads on which things are happening. By the time you get back a `Task` from `SendAsync` (or, well, anywhere else), it's *that* method's responsibility to arrange for any code that needs to run to have a thread available at the appropriate times. By the time you hand a `Task` or other awaitable to an `await` expression, the ship has already sailed for any scheduling decisions. – Damien_The_Unbeliever Jan 23 '19 at 08:53
  • @ErikPhilips It's a **Http Web load tool** , I want to use more threads to do more request , instead of rely on the `ThreadPool` . If it increase the threads in double than specified , it may get worse result – John Jan 23 '19 at 09:33
  • 1
    @John you're **nominclature is incorrect**. [You want more **requests**, it doesn't matter how you produce them](https://blogs.msdn.microsoft.com/timomta/2017/10/23/controlling-the-number-of-outgoing-connections-from-httpclient-net-core-or-full-framework/). Saying threads doesn't have any bearing to the number of requests. – Erik Philips Jan 23 '19 at 09:34

1 Answers1

2

I have an app (http web load test app) that need new Thread()

Why?

HttpClient only have async method, so how do I run the action synchronously

Why.

Or How to call asynchronous method from synchronous method in C#?.

I tried use full Task but the thread number it use is low (30 thread only),

A task is not a thread. We can easily test this by running methods on the thread pool. First we set the ThreadPool to only allow a single thread.

class Program
{
  private const int MaxThreads = 1;

  static void Main(string[] args)
  {
    ThreadPool.SetMinThreads(MaxThreads, 1);
    Console.WriteLine(ThreadPool.SetMaxThreads(MaxThreads, 1));
    Task.Run(() => SomeMethod(new StateInfo { Order = 0, WaitFor = 3000 }));
    Task.Run(() => SomeMethod(new StateInfo { Order = 1, WaitFor = 3000 }));
    Task.Run(() => SomeMethod(new StateInfo { Order = 2, WaitFor = 3000 }));
    Console.WriteLine("Main thread does some work, then sleeps.");
    Thread.Sleep(5000);
    Console.WriteLine("Main thread exits.");
  }

  static void SomeMethod(Object stateInfo)
  {
    var si = (StateInfo)stateInfo;
    Console.WriteLine($"Hello from the thread pool. {si.Order}");
    Thread.Sleep(si.WaitFor);
  }

  public class StateInfo
  {
    public int Order { get; set; }
    public int WaitFor { get; set; }
  }
}

Output

True

Main thread does some work, then sleeps.

Hello from the thread pool. 1

Hello from the thread pool. 2

Main thread exits.

Since we have 1 thread and we've told the first two methods to wait a total of 6 seconds, but the main thread exits after 5 seconds, we never get a message from the 3rd method. We can easily test this by changing MaxThreads = 2 which yields something like the following (we get 3 results, but not necessarily in order):

True

Main thread does some work, then sleeps.

Hello from the thread pool. 1

Hello from the thread pool. 2

Hello from the thread pool. 3

Main thread exits.

Now that we've guaranteed we're using a single thread, lets see how many requests we can do simultaneously synchronously.

static void SomeMethod(Object stateInfo)
{
  var si = (StateInfo)stateInfo;
  Console.WriteLine($"Hello from the thread pool. {si.Order}");
  httpClient.GetStringAsync($"https://www.google.com");
  Console.WriteLine($"Hello from the thread pool. {si.Order} finished");
}

Since we aren't async/await the request, it runs synchronously so the output is predictably:

True

Main thread does some work, then sleeps.

Hello from the thread pool. 1

Hello from the thread pool. 1 finished

Hello from the thread pool. 2

Hello from the thread pool. 2 finished

Hello from the thread pool. 3

Hello from the thread pool. 3 finished

Main thread exits.

That doesn't really load test anything because synchronous calls wait until the previous one finishes. In order to load test we want many concurrent calls. This is easily done with a single thread using async await.

Update the method:

static async Task SomeMethod(Object stateInfo)
{
  var si = (StateInfo)stateInfo;
  Console.WriteLine($"Hello from the thread pool. {si.Order}");
  await httpClient.GetStringAsync($"https://www.google.com");
  Console.WriteLine($"Hello from the thread pool. {si.Order} finished");
}

Use linq to make a list of requests, and wait for all of them to finish.

static void Main(string[] args)
{
  ThreadPool.SetMinThreads(MaxThreads, 1);
  Console.WriteLine(ThreadPool.SetMaxThreads(MaxThreads, 1));

  Console.WriteLine("Start Requests");
  var requests = Enumerable.Range(0, 200)
    .Select(async (x) => await Task.Run(() => SomeMethod2(new StateInfo { Order = x, WaitFor = 0 })))
    .ToArray();

  Console.WriteLine("Wait for them.");
  Task.WaitAll(requests.ToArray());

  Console.WriteLine("Main thread exits.");
  Console.ReadKey();
}

Yields (I didn't want to put 400 lines of code here)

True

Start Requests

Wait for them.

Hello from the thread pool. 0

Hello from the thread pool. 1

Hello from the thread pool. 2

.... repeating to

Hello from the thread pool. 199

Hello from the thread pool. 178 finished

Hello from the thread pool. 5 finished

Hello from the thread pool. 3 finished

Hello from the thread pool. 15 finished

Hello from the thread pool. 26 finished

Hello from the thread pool. 4 finished

.... repeating until all 200 requests are finished

Main thread exits.

200 Http Requests on a single thread. Why do you need more threads?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • Thanks for your comments and Answer, I can understand the difference between `Thread` and `Task` . Maybe I'm wrong , I previously think the reason of query number can not increase to a number as I desired is the threads number, and the `async` method(and `ThreadPool`) is designed to save `Thread`, so I decide to use `Thread` directly to increase the concurrency. I will do it today and to see the `thread` and `Task` can affect the result or not. – John Jan 24 '19 at 02:20
  • I tried your solution, but instead of `Order` number (as you used it) I printed `System.Threading.Thread.CurrentThread.ManagedThreadId` Even tough I put: `System.Threading.ThreadPool.SetMinThreads(1, 1); System.Threading.ThreadPool.SetMaxThreads(1, 1);` I get two different IDs, which means two threads are used. I have .NET Core console application. – Nikola Mar 03 '22 at 14:09
  • @Nikola That might show two different IDs. It might not. You can limit the number of threads your application uses, you can't tell it which thread that .Net has available to use. .Net might have 30 threads, and if you limit it to 1, [it doesn't mean you'll always have the same thread using async calls](https://stackoverflow.com/questions/48785663/why-is-the-initial-thread-not-used-on-the-code-after-the-awaited-method). – Erik Philips Mar 03 '22 at 20:48