在异步任务期间释放线程 [英] Releasing threads during async tasks

查看:154
本文介绍了在异步任务期间释放线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  • 请求的主线程将产生子流程并等待它们完成.
    • 那些子流程进行一些处理
    • 然后与远程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在远程源等待时将控制传递给调用堆栈,如果我启动了一堆> Tasks,然后调用Tasks.WaitAll,那么在远程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返回的那些)仍建立在相同的异步I/O系统上,然后是async/await使它们的使用更加愉快;您可以使用await保持相同的方法,而不用弄乱回调(APM)或事件处理程序(EAP).

作为一个有趣的旁注,Task实际上实现了IAsyncResult,并且从高级角度看,APM和TAP非常相似(IAsyncResultTask都表示正在运行"操作)./p>

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

(顺便说一句,考虑使用HttpClient,它是从头开始考虑TAP的,而不是HttpWebRequest/WebClient,后者已将TAP固定在上面)./p>

但是...

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

使用这种管道",您可能需要考虑转换为TPL Dataflow.数据流既了解同步(TAP)工作又了解异步(TAP)工作,并且对节流具有内置支持.数据流方法可能比TAP本身进一步简化了代码.

I have a system that spawns a LOT of sub-processes that must run in parallel

  • The main thread for a request will spawn sub-processes and wait for them to complete.
    • Those sub-processes do some processing
    • then talk to a remote API
    • then do more processing of the results from the API
  • then the main thread continues when all sub-processes are complete (or timeout hit)

We have trouble with thread counts, so we want to reduce the number of threads active by trying to release the threads whilst we wait for the remote API.

Originally we used WebRequest.GetResponse() for the API call which naturally is holding an idle thread whilst waiting for the API.

The we started using an EAP model (Event-based Async Programming ... all the various .NET methods that use IAsyncResult) , where we call BeginGetResponse(CallbackTrigger), with WaitHandles passed back to the main thread which then triggered the post-API processing.

As we understand it, this means that the sub-process thread terminates, and the Callback is triggered by a network-card-level interupt which triggers a new thread to initiate the callback. i.e. there's no thread sitting waiting to run CallbackTrigger whilst we wait for the API call.

If people could confirm this understanding that would be good?

We are now considering moving to a TPL model (Task Parallel Library ... Task<T>), using WebRequest.GetResponseAsync() which is awaitable. I'm under the impression that this is part of what await\ async does... that await passes control back up the call stack whilst the remote source waits, and that if I initiate a bunch of awaitable Tasks and then call Tasks.WaitAll then that won't be holding onto a thread for each Task whilst that task is awaiting on the remote API.

Have I correctly understood this?

解决方案

If people could confirm this understanding that would be good?

Yes. Note that the IAsyncResult/Begin*/End* pattern is APM, not EAP. EAP would be WebClient's approach where the DownloadAsync method triggers a DownloadCompleted event when it's done.

APM/EAP are hard ways of doing asynchronous work, but are in fact asynchronous (meaning, they do not take up a thread just to block on I/O completing). They're "hard" because they makes your code much more complex - to the point that most developers never used them and just stuck with synchronous code instead.

Have I correctly understood this?

Yes. In general, all asynchronous I/O in .NET is implemented using a single I/O completion port which exists as part of the thread pool. This is true whether the API is APM, EAP, or TAP.

The whole idea of async/await with TAP is that the core Tasks (like those returned from GetResponseAsync) are still built on the same asynchronous I/O system, and then async/await makes consuming them much more pleasant; you can stay in the same method with await instead of messing with callbacks (APM) or event handlers (EAP).

As an interesting side note, Task actually implements IAsyncResult, and from a high-level perspective APM and TAP are very similar (both IAsyncResult and Task represent an operation "in flight").

You should find your TAP code significantly simpler (and easier to maintain!) than your current APM/EAP code, with no noticeable change in performance.

(On a side note, consider moving to HttpClient, which was designed from the ground up with TAP in mind, rather than HttpWebRequest/WebClient, which have had TAP bolted-on to them).

However...

I have a system that spawns a LOT of sub-processes that must run in parallel...

With this kind of a "pipeline", you may want to consider converting to TPL Dataflow. Dataflow understands both synchronous and asynchronous (TAP) work, and has built-in support for throttling. A Dataflow approach may simplify your code even further than TAP on its own.

这篇关于在异步任务期间释放线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆