在异步任务期间释放线程

时间:2016-05-13 09:39:40

标签: c# multithreading async-await httpwebrequest task-parallel-library

我的系统产生了许多必须并行运行的子流程

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

我们遇到线程计数问题,因此我们希望在等待远程API时尝试释放线程来减少活动线程数。

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

我们开始使用EAP模型(基于事件的异步编程......使用IAsyncResult的所有各种.NET方法),我们调用BeginGetResponse(CallbackTrigger),并将WaitHandle传递给主线程然后触发API后处理。

正如我们所理解的那样,这意味着子进程线程终止,并且回调由网卡级中断触发,该中断触发新线程以启动回调。即在我们等待API调用时,没有线程等待运行CallbackTrigger

如果有人能证实这种理解会很好吗?

我们现在正在考虑使用Task<T> WebRequest.GetResponseAsync()能够转移到TPL模型(任务并行库... await)。我的印象是,这是await \ async所做的一部分... await在远程源等待的同时控制备份调用堆栈,如果我启动一堆awaitTasks,然后调用TasksWaitAll然后在该任务正在等待时,不会保留每个任务的线程在远程API上

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

2 个答案:

答案 0 :(得分:2)

  

如果有人能证实这种理解会很好吗?

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

APM / EAP是 hard 进行异步工作的方式,但实际上是异步的(意思是,它们不会占用线程只是为了阻止I / O完成)。它们“很难”,因为它们使您的代码变得更加复杂 - 大多数开发人员从未使用它们而只是坚持使用同步代码。

  

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

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

async / await与TAP的整体想法是核心Task(就像从GetResponseAsync返回的那些)仍然构建在同一个异步I /上O系统,然后async / await使他们更愉快地消费;您可以使用await保持相同的方法,而不是使用回调(APM)或事件处理程序(EAP)。

作为一个有趣的旁注,Task实际上实现了IAsyncResult,从高层次的角度来看,APM和TAP非常相似({1}}和IAsyncResult都代表了操作“在飞行中”)。

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

(另请注意,请考虑转移到Task,这是从头开始设计的,而不是HttpClient / HttpWebRequest,它们已经使用了TAP螺栓 - 对他们来说)。

...然而

  

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

使用这种“管道”,您可能需要考虑转换为TPL数据流。 Dataflow了解同步和异步(TAP)工作,并且内置了对限制的支持。数据流方法可以比TAP更加简化您的代码。

答案 1 :(得分:0)

继@Stephen Cleary的回答之后,我进行了一次简短的测试,以进一步证明这一点。

以下代码,在运行Synchronous方法时,不修改SetMinThreads,并且当定位需要几秒钟返回的网站时,将为每个请求保持一个线程打开。它将显示越来越多的线程处于活动状态,它会立即启动前几个任务,但在达到ThreadPool的限制时“扼住”,并且只允许每半秒启动一次新线程,或者旧请求结束。

设置更高的MinThreadCount会按预期延迟问题。

保持MinThread计数未设置,但切换到异步(APM)方法或Await(TAP)方法会导致所有任务立即启动,并且任何点的活动线程数都保持低位。

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