当前位置: 首页 > 知识库问答 >
问题:

为什么我的TPL程序比它的Async/Await对应程序使用更多的线程池资源?

蒋昊天
2023-03-14

我正在编写一个程序,演示在服务器可伸缩性上下文中使用异步IO的好处。程序并发地使用一个异步方法,然后报告参与异步处理的线程的ID。

为了说明,请考虑以下内容:

static async Task<TimeSpan> AsyncCalling(TimeSpan time)
{
    using (SleepService.SleepServiceClient client = new SleepService.SleepServiceClient())
    {
        TimeSpan response = await client.SleepAsync(time);
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

        response += await client.SleepAsync(TimeSpan.FromTicks(time.Ticks / 2));
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

        return response;
    }
}
int numberOfWorkItems = 50;
for (int i = 0; i < numberOfWorkItems; ++i)
{
    TimeSpan value = TimeSpan.FromSeconds((i % 3) + 1);
    ThreadPool.QueueUserWorkItem(arg => { TimeSpan t = AsyncCalling(value).Result; });

    Thread.Sleep(300);
}
static Task<TimeSpan> TaskAsyncCalling(TimeSpan time)
{
    SleepService.SleepServiceClient client = new SleepService.SleepServiceClient();

    return client.SleepAsync(time)
                 .ContinueWith(t =>
                 {
                     TimeSpan result = t.Result;
                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

                     return client.SleepAsync(TimeSpan.FromTicks(time.Ticks / 2))
                                  .ContinueWith(t1 =>
                                  {
                                      result += t1.Result;
                                      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                                      (client as IDisposable).Dispose();

                                      return result;
                                  });
                 })
                 .Unwrap();
}

在同一上下文中调用taskasyncalling时,输出结果完全不同。这些任务通常需要更长的时间来执行,并且唯一线程ID的总数通常在30个左右(同样,对于我的2核机器)。

为什么会出现这种差距呢?我知道await不是task 的普通包装,但是线程池是共同的分母,我希望在TPL实现中也能出现同样聪明的线程重用。

有没有另一种方法可以重写TPL方法,以达到相同的结果而不阻塞?

public TimeSpan Sleep(TimeSpan time)
{
    Thread.Sleep(time);
    return time;
}

共有1个答案

夏和雅
2023-03-14

在这种情况下,经典TPL版本比async/await版本使用更多的线程,因为每个continuewith延续都是在单独的池线程上执行的。

使用TaskContinuationSoptions.ExecuteSynchronology修复此问题:

static Task<TimeSpan> TaskAsyncCalling(TimeSpan time)
{
    SleepService.SleepServiceClient client = new SleepService.SleepServiceClient();

    return client.SleepAsync(time)
        .ContinueWith(t =>
        {
            TimeSpan result = t.Result;
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

            return client.SleepAsync(TimeSpan.FromTicks(time.Ticks / 2))
                .ContinueWith(t1 =>
                {
                    result += t1.Result;
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                    (client as IDisposable).Dispose();

                    return result;
                }, TaskContinuationsOptions.ExecuteSynchronously);
         }, TaskContinuationsOptions.ExecuteSynchronously)
         .Unwrap();
 }

OTOH,await连续操作通常是同步执行的(如果操作是在同一同步上下文上完成的,或者如果在两个执行点都没有同步)。所以它需要少获取两个线程。

一个很好的相关阅读:“为什么是TaskContinuationSoptions.ExecuteSynchronology opt-in?”

 类似资料:
  • 最近在接受采访时,我得到了这个问题。 Q: 您编写过多线程应用程序吗? 甲:是的 问:介意解释更多吗? 答:我使用了< code>Tasks(任务并行库)来执行一些任务,比如< code >加载UI时等待来自互联网的一些信息。这提高了我的应用程序的可用性。 问:但是,仅仅您使用了就意味着您编写了应用程序? 我:(不知道该说什么) 那么,究竟什么是多线程应用程序?它与使用不同吗?

  • 问题内容: 我最近继承了一个小型Java程序,该程序从大型数据库中获取信息,进行一些处理并生成有关该信息的详细图像。原始作者使用单个线程编写了代码,然后对其进行了修改,以使其可以使用多个线程。 他在代码中定义了一个常量; 然后,它设置用于创建映像的线程数。 我理解他的理由,即线程数不能大于可用处理器的数目,因此将其设置为可以充分发挥处理器潜力的数量。这样对吗?还是有更好的方法来充分利用处理器的潜力

  • 我最近使用vert. x(基于java)测试了一个简单的HTTP服务器。我对超文本传输协议服务器的吞吐量和api延迟感到惊讶,它非常快。 超文本传输协议-server的同一段代码在java应用程序上以单线程运行,无锁且无阻塞。性能不到vert. x 1的三分之一。 我不明白的是,vert. x优于非反应性java应用程序的核心技术区别是什么? 统计数据: 测试是使用Jmeter完成的。jmetm和

  • 我正在使用多个线程在不同的表中插入插入记录。此外,我正在使用批处理的记录插入,以提高效率。 注意:要插入的记录数以百万为单位。 我的问题是,在这种多线程环境中,我应该使用连接池吗? 我关心的问题: 每个线程将运行相当长的时间来执行数据库操作。所以,如果我的连接池的大小是2,线程的数量是4,那么在给定的时刻只有2个线程将运行。因此,其他两个线程将会在很长一段时间内保持理想状态以获得连接,因为针对百万

  • 我的工作与Javawebapp与Apache Tomcat一起运行。Tomcat线程池的最大线程数为800,minSpareThread为25。当它运行时,它通常在给定时间运行大约400个线程。 比方说,我有一个计算成本很高的非阻塞任务,我必须在我的Tomcat应用程序中完成,在这个应用程序中,ForkJoinPool。commonPool用于更有效地解决任务。 因为我的ApacheTomcat应

  • 问题内容: 在Java中拥有多个线程池的优缺点是什么?我已经看过代码,其中有多个线程池用于不同的“类型”任务,而且我不确定它是更好的设计还是只是开发人员感到懒惰。一个示例是将ScheduledThreadPoolExecutor用于定期执行的任务或具有超时的任务,而将另一ThreadPoolExecutor用于其他任务。 问题答案: 具有单独的专用线程池的目的是,使活动不会因线程不足而被饥饿,因为