【问题标题】:How to run async method in specified thread如何在指定线程中运行异步方法
【发布时间】:2019-01-22 06:07:51
【问题描述】:

我有一个需要 new Thread() 的应用程序(http web 负载测试应用程序),而 HttpClient 只有异步方法,那么我如何同步运行操作

ps:我尝试使用完整的Task,但它使用的线程数很低(仅 30 个线程), 所以我想试试Thread 看看它是否可以更快。 .GetAwaiter().GetResult() 会花费 2 个线程(100 个线程变成 200 个线程)吗?


以前我用过

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)

所以我想直接切换使用Thread

for(var i = 0; i< 200;i++)
{
    new Thread(()=>
    {
        while(thereStillHaveRequestToMake)
        {
            httpclient.SendAsync().GetAwaiter.GetResult()
        }
    });
}

【问题讨论】:

  • 您的假设是错误的,.GetAwaiter().GetResult() 不会花费任何线程,如果编写正确,异步 io 调用将不会使用任何线程。如果您正在编写负载测试器,我建议您使用 DataFlow 之类的东西,它可以让您对线程进行更好的细粒度控制,并且可以很好地与异步配合使用。但是这一切都很难说,因为我们没有看到您尝试使用的代码.. 虽然这不是 XY 问题的经典定义,但实际上您想出于错误的原因做错误的事情是 XY
  • @TheGeneral 我今天读到了stackoverflow.com/questions/34151179/…,我无法理解“.GetAwaiter().GetResult() 不会花费任何线程,如果编写正确,异步 io 调用将不会使用任何线程。”,我认为它会使用附加线程来完成await关键字之后的动作,因为当前线程被阻塞直到整个Task完成,除非Task是第一个真正的异步Task(不要使用async,await关键字)
  • 无法回答这个问题的原因是您没有给出原因或示例说明为什么此代码必须同步运行。否则它只是 How to call asynchronous method from synchronous method in C#? 的副本。
  • @John 你是命名不正确You want more requests, it doesn't matter how you produce them。说线程与请求的数量没有任何关系。

标签: c# multithreading asynchronous


【解决方案1】:

我有一个需要 new Thread() 的应用(http web 负载测试应用)

为什么?

HttpClient只有异步方法,如何同步运行动作

为什么。

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

我尝试使用完整的任务,但它使用的线程数很低(仅 30 个线程),

任务不是线程。我们可以通过在线程池上运行方法来轻松测试这一点。首先我们将 ThreadPool 设置为只允许单个线程。

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

输出

是的

主线程做一些工作,然后休眠。

来自线程池的你好。 1

来自线程池的你好。 2

主线程退出。

由于我们有 1 个线程,并且我们告诉前两个方法总共等待 6 秒,但主线程在 5 秒后退出,我们永远不会收到来自第 3 个方法的消息。我们可以通过更改 MaxThreads = 2 来轻松测试这一点,这会产生如下内容(我们得到 3 个结果,但不一定按顺序排列):

是的

主线程做一些工作,然后休眠。

来自线程池的你好。 1

来自线程池的你好。 2

来自线程池的你好。 3

主线程退出。

既然我们已经保证我们使用的是单线程,那么让我们看看我们可以同时同步处理多少个请求

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

由于我们不是 async/await 请求,它同步运行,所以输出是可预测的:

是的

主线程做一些工作,然后休眠。

来自线程池的你好。 1

来自线程池的你好。 1 个完成

来自线程池的你好。 2

来自线程池的你好。 2完成

来自线程池的你好。 3

来自线程池的你好。 3完成

主线程退出。

这并没有真正对任何东西进行负载测试,因为 同步 调用会等到前一个调用完成。为了进行负载测试,我们需要许多 并发 调用。这可以通过使用异步等待的单个线程轻松完成。

更新方法:

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

使用 linq 创建请求列表,然后等待所有请求完成。

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

产量(我不想在这里放 400 行代码)

是的

开始请求

等他们。

来自线程池的你好。 0

来自线程池的你好。 1

来自线程池的你好。 2

....重复到

来自线程池的你好。 199

来自线程池的你好。 178完成

来自线程池的你好。 5个完成

来自线程池的你好。 3完成

来自线程池的你好。 15个完成

来自线程池的你好。 26个完成

来自线程池的你好。 4 完成

....重复直到所有 200 个请求都完成

主线程退出。

单个线程上的 200 个 Http 请求。为什么需要更多线程?

【讨论】:

  • 感谢您的 cmets 和回答,我可以理解 ThreadTask 之间的区别。也许我错了,我之前认为查询数不能增加到我想要的数字的原因是线程数,而async方法(和ThreadPool)旨在保存Thread,所以我决定直接使用Thread来增加并发。我今天就做,看看threadTask会不会影响结果。
  • 我尝试了你的解决方案,但是我打印了Order 而不是Order 数字(正如你使用的那样)我甚至很难输入:System.Threading.ThreadPool.SetMinThreads(1, 1); System.Threading.ThreadPool.SetMaxThreads(1, 1); 我得到了两个不同的ID,这意味着两个线程是用过的。我有 .NET Core 控制台应用程序。
  • @Nikola 这可能会显示两个不同的 ID。它可能不会。您可以限制应用程序使用的线程数,但不能告诉它.Net 可以使用哪个线程。 .Net 可能有 30 个线程,如果将其限制为 1,it doesn't mean you'll always have the same thread using async calls
猜你喜欢
  • 2013-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-14
  • 1970-01-01
  • 1970-01-01
  • 2019-01-08
相关资源
最近更新 更多