繁体   English   中英

在异步任务期间释放线程

[英]Releasing threads during async tasks

我有一个系统,该系统产生许多必须并行运行的子流程

  • 请求的主线程将产生子流程并等待它们完成。
    • 这些子流程进行一些处理
    • 然后与远程API交谈
    • 然后对API的结果进行更多处理
  • 然后当所有子进程完成(或超时命中)后,主线程继续

我们在线程计数方面遇到麻烦,因此我们想通过在等待远程API时尝试释放线程来减少活动线程的数量。

最初,我们使用WebRequest.GetResponse()进行API调用,该调用自然是在等待API的同时持有空闲线程。

我们开始使用EAP模型(基于事件的异步编程...使用IAsyncResult的所有各种.NET方法),在此我们称为BeginGetResponse(CallbackTrigger) ,其中WaitHandle被传递回主线程,然后触发后线程。 API处理。

据我们了解,这意味着子进程线程终止,并且回调是由网卡级中断触发的,该中断触发了新线程来发起回调。 也就是说,在等待API调用时,没有线程在等待运行CallbackTrigger

如果人们可以确认这种理解,那会很好吗?

现在,我们正在考虑使用await可用的WebRequest.GetResponseAsync()迁移到TPL模型(任务并行库... Task<T> )。 我给人的印象是,这是await \\ async所做的工作的一部分……当远程源等待时, await将控制权传递回调用堆栈,如果我启动了一堆可await Tasks然后调用Tasks WaitAll然后将不会为每个Task保持线程, 而该任务正在远程API上等待

我是否正确理解了这一点?

如果人们可以确认这种理解,那会很好吗?

是。 请注意, IAsyncResult / Begin* / End*模式是APM,而不是EAP。 EAP是WebClient的方法,其中DownloadAsync方法在完成后会触发DownloadCompleted事件。

APM / EAP是完成异步工作的方法,但实际上是异步的(意味着,它们不会占用线程只是为了阻止I / O完成)。 它们之所以“困难”,是因为它们使您的代码复杂得多,以至于大多数开发人员从未使用过它们,而只是停留在同步代码上。

我是否正确理解了这一点?

是。 通常,.NET中的所有异步I / O都使用单个I / O完成端口实现,该端口作为线程池的一部分存在。 无论API是APM,EAP还是TAP,都是如此。

使用TAP进行async / await的整个想法是,核心Task (如从GetResponseAsync返回的GetResponseAsync )仍建立在同一异步I / O系统上,然后async / await使使用它们变得更加愉快。 您可以保持与await相同的方法,而不用弄乱回调(APM)或事件处理程序(EAP)。

有趣的一点是, Task实际上实现了IAsyncResult ,从高级角度看,APM和TAP非常相似( IAsyncResultTask表示“正在运行”的操作)。

您应该发现TAP代码比当前的APM / EAP代码明显更简单(并且更易于维护!),并且性能没有明显变化。

HttpClient ,考虑迁移到HttpClient ,它是从头开始设计的,而不是TAP固定在上面的HttpWebRequest / WebClient )。

然而...

我有一个产生很多必须并行运行的子流程的系统...

通过这种“管道”,您可能要考虑转换为TPL Dataflow。 数据流既了解同步(异步)工作,又具有对节流的内置支持。 与TAP相比,Dataflow方法甚至可以进一步简化代码。

除了@Stephen Cleary的答案,我还进行了简短的测试来进一步证明这一点。

下面的代码在运行Synchronous方法时,无需对SetMinThreads进行修改,并且在定位需要花费几秒钟返回时间的网站时,将为每个请求保持一个打开的线程。 它将显示越来越多的活动线程,它会立即启动前几个任务,但在达到ThreadPool的限制时会“阻塞”,并且仅每半秒或在旧请求结束时才启动新线程。

设置更高的MinThreadCount可以按预期推迟该问题。

保持MinThread Count不变,但是切换到Asynchronous(APM)方法或Await(TAP)方法将立即启动所有Task,并且在任何时候处于活动状态的线程数都保持较低。

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

namespace LockTraceParser
{
  internal class AsyncThreadsTester
  {
    public void Run()
    {
      //ThreadPool.SetMinThreads(100, 100);

      Console.WriteLine("Beginning Test: ");
      LogThreadCounts();

      Test();
    }

    private void Test()
    {
      LogThreadCounts();

      for (int i = 0; i < 65; i++)
      {
        //StartParallelUserWorkItem(i);
        StartTask(i);
        Thread.Sleep(100); //sleep a while so that the other thread is working
        LogThreadCounts();
      }

      for (int i = 0; i < 40; i++)
      {
        Thread.Sleep(1100); //sleep a while so that the other thread is working
        LogThreadCounts();
      }
    }

    private void StartTask(int label)
    {
      var taskLabel = "Task " + label;
      Console.WriteLine("Enqueue " + taskLabel);
      Task.Run(() => GetResponseAwait(taskLabel));
    }

    private static void LogThreadCounts()
    {
      int worker;
      int io;
      ThreadPool.GetAvailableThreads(out worker, out io);
      Console.WriteLine("Worker Threads Available:" + '\t' + worker + '\t' + "IO Threads Available:" + '\t' + io + '\t' +
                        "Threads held by Process: " + '\t' + Process.GetCurrentProcess().Threads.Count);
    }


    private void GetResponseSync(object label)
    {
      Console.WriteLine("Start Sync     " + label);
      try
      {
        var req = GetRequest();
        using (var resp = req.GetResponse())
        {
          Console.WriteLine(resp.ContentLength);
        }
      }
      catch (Exception e)
      {
        Console.WriteLine("Error response " + label);
      }
      Console.WriteLine("End response   " + label);
    }

    private void BeginResponseAsync(object label)
    {
      Console.WriteLine("Start Async     " + label);
      try
      {
        var req = GetRequest();
        req.BeginGetResponse(EndGetResponseAsync, req);
      }
      catch (Exception e)
      {
        Console.WriteLine("Error Async " + label);
      }
    }

    private void EndGetResponseAsync(IAsyncResult result)
    {
      Console.WriteLine("Respond Async   ");
      var req = (WebRequest)result.AsyncState;

      using (var resp = req.EndGetResponse(result))
      {
        Console.WriteLine(resp.ContentLength);
      }
      Console.WriteLine("End Async   ");
    }

    private async Task GetResponseAwait(object label)
    {
      Console.WriteLine("Start Await     " + label);
      try
      {
        var req = GetRequest();
        using (var resp = await req.GetResponseAsync())
        {
          Console.WriteLine(resp.ContentLength);
        }
      }
      catch (Exception e)
      {
        Console.WriteLine("Error Await " + label);
      }
      Console.WriteLine("End Await   " + label);
    }

    private WebRequest GetRequest()
    {
      var req = WebRequest.Create("http://aslowwebsite.com");
      req.Timeout = (int)TimeSpan.FromSeconds(60).TotalMilliseconds;

      return req;
    }
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM