Async/Await关于系统资源消耗和效率

vic*_*tor 9 c# asynchronous

简短版本:当异步方法在循环中被调用成千上万次时异步调用如何缩放,这些方法可能会调用其他异步方法?我的线程池会爆炸吗?

我一直在阅读和试验TPL和Async,在阅读了很多材料之后,我仍然对某些方面感到困惑,这些方面我找不到太多信息,比如异步调用如何缩放.我会尽力直截了当.

异步调用
对于IO,我读到最好使用异步而不是新线程/启动任务,但据我所知,不使用不同线程执行异步操作是不可能的,这意味着异步必须使用其他线程/启动任务在某一点.所以我的问题是:代码A如何比代码B更好地处理系统资源?

代码A.

// an array with 5000 urls.
var urls = new string[5000];

// list of awaitable tasks.
var tasks = new List<Task<string>>(5000);

HttpClient httpClient;

foreach (string url in urls)
{
    tasks.Add(httpClient.GetStringAsync(url));
}

await Task.WhenAll(tasks);
Run Code Online (Sandbox Code Playgroud)

代码B.

...same variables as code A...

foreach (string url in urls)
{
    tasks.Add(
              Task.Factory.StartNew(() =>
              {
                // This method represents a
                // synchronous version of the GetStringAsync.
                httpClient.GetString(url);
              })
             );
}

await Task.WhenAll(tasks);
Run Code Online (Sandbox Code Playgroud)

这引出了我的问题:
1 - 是否应该在循环中避免异步调用?
2 - 是否有一个合理的最大异步调用应该一次触发,或者是否正在触发任意数量的异步调用?这个规模如何?
3 - 引擎盖下的异步方法是否为每个呼叫启动任务?

我用1000个网址测试了这个,并且使用过的线程池工作线程的数量从未达到过30,IO完成线程的数量总是大约为5.

我的实践实验

我用一个简单的异步控制器创建了一个Web应用程序.该页面由一个带有textarea的表单组成,用户可以在其中输入他希望请求/做一些工作的所有URL.

在submition时,使用HttpClient.GetUrlAsync方法在循环中请求URL,就像上面的代码A一样.

一个有趣的观点是,如果我提交1000个网址,则需要大约3分钟才能完成所有请求.

另一方面,如果我从3个不同的标签(即客户端)提交3个表单,每个表单有1000个网址,结果需要更长的时间(大约10分钟),这真让我感到困惑,因为根据msdn定义,它不应该花费超过3分钟,特别是当同时处理所有请求时,来自线程池的使用线程数约为25,这意味着资源根本没有被很好地探索!

它现在的工作方式,这种类型的应用程序远非可扩展(假设我有大约5000个客户端一直请求一堆URL),我没有看到asyncis如何触发多个IO请求.

关于申请的进一步说明

客户端:
1.用户进入网站
2.在文本区域中输入1000个网址
3.提交网址

服务器端:
1.接收URL作为数组
2.执行代码

foreach (string url in urls)
{
    tasks.Add(GetUrlAsync(url));
}

await Task.WhenAll(tasks);
//at this point the thread is
// returned to the pool to receive
// further requests.
Run Code Online (Sandbox Code Playgroud)
  1. 通知客户工作已完成

拜托,赐教!谢谢.

Ste*_*ary 5

根据我的理解,不使用不同的线程执行异步操作是不可能的,这意味着异步必须在某些时候使用其他线程/启动任务.

.正如我在博客中描述的那样,纯异步方法不会阻止线程.

所以我的问题是:代码A如何比代码B更好地处理系统资源?

A比B使用更少的线程

(另请注意,请不要使用StartNew.它可怕地过时并且具有非常危险的默认参数值.请Task.Run改用.如果您从博客文章或文章中获得了这个想法/代码,请传递这个词.StartNew是一个癌症似乎正在接管互联网.)

应该在循环中避免异步调用吗?

不,那没关系.

是否有一个合理的最大异步调用,一次应该被触发,或者是否正在触发任意数量的异步调用?

只要你的后端资源可以处理它,任何数量都可以.

这个规模如何?

.NET上的异步I/O几乎总是使用下面的IOCP(I/O完成端口),这通常被认为是Windows上可用的最可扩展的I/O形式.

引擎盖下的异步方法是否为每个呼叫启动任务?

是的,不是.每个异步方法的执行都由一个实例表示Task,但这些不代表正在运行的任务 - 它们不代表一个线程.

我称为异步任务Promise Tasks,而不是委托任务(实际上在线程池上运行的任务).

真让我困惑

在测试URL请求时要注意的一件事是,对.NET内置的URL请求进行自动限制.尝试设置ServicePointManager.DefaultConnectionLimitint.MaxValue.