我的系统产生了许多必须并行运行的子流程
我们遇到线程计数问题,因此我们希望在等待远程API时尝试释放线程来减少活动线程数。
最初我们使用WebRequest.GetResponse()
进行API调用,这在等待API时自然会持有空闲线程。
我们开始使用EAP模型(基于事件的异步编程......使用IAsyncResult的所有各种.NET方法),我们调用BeginGetResponse(CallbackTrigger)
,并将WaitHandle
传递给主线程然后触发API后处理。
正如我们所理解的那样,这意味着子进程线程终止,并且回调由网卡级中断触发,该中断触发新线程以启动回调。即在我们等待API调用时,没有线程等待运行CallbackTrigger
。
如果有人能证实这种理解会很好吗?
我们现在正在考虑使用Task<T>
WebRequest.GetResponseAsync()
能够转移到TPL模型(任务并行库... await
)。我的印象是,这是await
\ async
所做的一部分... await
在远程源等待的同时控制备份调用堆栈,如果我启动一堆await
能Tasks
,然后调用Tasks
。WaitAll
然后在该任务正在等待时,不会保留每个任务的线程在远程API上。
我是否正确理解了这一点?
答案 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;
}
}
}