6 .net c# clr mono async-await
我编写了一个简单的基于异步的负载测试库,它还有一个控制台界面,可以从命令行进行测试.
基本上,它同时运行大量请求,聚合它们,并显示摘要和简单的直方图.没有什么花哨.但是我在本地系统中运行了很多测试,因此我想确保测试工具尽可能地使用尽可能少的资源来获得相对准确的基准测试.因此它使用与开始/结束方法的裸异步来保持最小的开销.
所有完成,完全异步,它的工作,并避开(大多数情况下).但正常会话中的线程数远远超过40个.因此,考虑到本地计算机还在运行正在测试的服务器,对于具有4个硬件线程的计算机来说,资源的浪费非常简洁.
我已经在AsyncContext中运行该程序,它基本上只是一个简单的排队上下文,将所有内容放在同一个线程上.因此,所有异步回发都在主线程上.完善.
现在,我所要做的就是限制ThreadPool的最大线程,并查看它的执行情况.将其限制为实际核心,具有4个工作线程和4个IOCP线程.
结果?
例外:"ThreadPool中没有足够的空闲线程来完成操作."
嗯,这不是一个新问题,并且遍布互联网.但是不是ThreadPool的全部意义,你可以将事情放到池的队列中,并且只要线程可用就会执行它?
事实上,该方法的名称是" Queue "UserWorkItem.文档适当地说:"将一个执行方法排队.该方法在线程池线程可用时执行."
现在,如果没有足够的可用线程可用,理想情况下,预期的可能是程序执行速度减慢.IOCP和异步任务应该排在队列中,但为什么它以这样的方式实现,它会被击倒,而失败呢?当它被称为ThreadPool作为队列时,增加线程数不是解决方案.
编辑 - 澄清:
我完全了解线程池的概念,以及为什么CLR会旋转更多的线程.这应该.我同意,当存在大量IO绑定任务时,这是正确的事情.但问题是,如果你确实在某种程度上限制了ThreadPool中的线程,那么只要有空闲线程可用,就应该将任务排队等待执行,而不是抛出异常.并发可能会受到影响,甚至可能会减慢结果,但QueueWorkUserItem意图进行队列,只有在新线程可用或失败时才能工作 - 因此,我的推测断言它是一个实现错误,如标题中所述.
更新1:
与Microsoft支持论坛中记录的问题相同,例如:http://support.microsoft.com/default.aspx? scid = kb; EN-US; 815637
解决方法建议,显然是增加线程数,因为它无法排队.
注意:这是在一个非常古老的运行时,并且下面给出了在4.5.1运行时重现相同问题的方法.
更新2:
然对Mono的运行时代码相同的块,和线程池似乎没有任何问题存在.它排队等待并执行.该问题仅在Microsoft CLR下发生.
更新3:
在@ Noseratio指出无法在.NET 4.5.1下重现相同代码的有效问题之后,下面是一段代码将重现该问题.为了打破在按预期排队时工作的代码,所有必须要做的就是向排队的委托添加一个真正的异步调用.
例如,只需将以下行添加到委托的末尾,就应该以异常结束:
(await WebRequest.Create("http://www.google.com").GetResponseAsync()).Close();
Run Code Online (Sandbox Code Playgroud)
复制代码:
这是从MSKB文章稍微修改过的代码,在Windows 8.1中的.NET 4.5.1下会很快失败.
(随意更改网址和线程限制).
public static void Main()
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(2, 2);
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Queued {0}", i);
ThreadPool.QueueUserWorkItem(PoolFunc);
}
Console.ReadLine();
}
private static async void PoolFunc(object state)
{
int workerThreads, completionPortThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
Console.WriteLine(
"Available: WorkerThreads: {0}, CompletionPortThreads: {1}",
workerThreads,
completionPortThreads);
Thread.Sleep(1000);
string url = "http://localhost:8080";
HttpWebRequest myHttpWebRequest;
// Creates an HttpWebRequest for the specified URL.
myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
// Sends the HttpWebRequest, and waits for a response.
Console.WriteLine("Wait for response.");
var myHttpWebResponse = await myHttpWebRequest.GetResponseAsync();
Console.WriteLine("Done.");
myHttpWebResponse.Close();
}
Run Code Online (Sandbox Code Playgroud)
对此行为的任何洞察都可以为此提供推理,我们非常感激.谢谢.
在您的示例代码中,不是调用QueueUserWorkItem异常的调用,而是调用异常的调用await myHttpWebRequest.GetResponseAsync().如果查看异常详细信息,您可以确切地看到抛出此异常的方法
System.InvalidOperationException was unhandled by user code
_HResult=-2146233079
_message=There were not enough free threads in the ThreadPool to complete the operation.
HResult=-2146233079
IsTransient=false
Message=There were not enough free threads in the ThreadPool to complete the operation.
Source=System
StackTrace:
at System.Net.HttpWebRequest.BeginGetResponse(AsyncCallback callback, Object state)
at System.Threading.Tasks.TaskFactory`1.FromAsyncImpl(Func`3 beginMethod, Func`2 endFunction, Action`1 endAction, Object state, TaskCreationOptions creationOptions)
at System.Threading.Tasks.TaskFactory`1.FromAsync(Func`3 beginMethod, Func`2 endMethod, Object state)
at System.Net.WebRequest.<GetResponseAsync>b__8()
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at ConsoleApplication1.Program.<PoolFunc>d__0.MoveNext() in c:\Users\Justin\Source\Repos\Azure\ConsoleApplication1\ConsoleApplication1\Program.cs:line 39
InnerException:
Run Code Online (Sandbox Code Playgroud)
实际上,如果我们看一下这个HttpWebRequest.BeginGetResponse方法,我们可以看到以下内容
if (!RequestSubmitted && NclUtilities.IsThreadPoolLow())
{
// prevent new requests when low on resources
Exception exception = new InvalidOperationException(SR.GetString(SR.net_needmorethreads));
Abort(exception, AbortState.Public);
throw exception;
}
Run Code Online (Sandbox Code Playgroud)
这个故事的寓意是线程池是一个共享资源,其他代码(包括.Net框架的一部分)也使用 - 将最大线程数设置为2是Raymond Chen称之为本地问题的全局解决方案结果打破了系统其他部分的期望.
如果你想显式控制正在使用的线程,那么你应该创建自己的实现,但是除非你真的知道你在做什么,否则最好让.Net框架处理线程管理.
| 归档时间: |
|
| 查看次数: |
3546 次 |
| 最近记录: |