C# 引发的异常似乎正在阻止其他线程
背景/简介: 非常奇怪的问题,在抛出特定异常时,所有其他线程似乎都停止执行,直到异常得到处理。该应用程序是一个代理服务器,通过tcp套接字池传输多个请求(在任何一个时间范围内有数百个请求),tcp套接字通过socks连接连接到N个其他代理客户端。通过传递一个委托(而不是使用异常)也尝试了这种方法,但同样的性能结果很差 正常操作下的日志片段:C# 引发的异常似乎正在阻止其他线程,c#,windows,sockets,exception,proxy,C#,Windows,Sockets,Exception,Proxy,背景/简介: 非常奇怪的问题,在抛出特定异常时,所有其他线程似乎都停止执行,直到异常得到处理。该应用程序是一个代理服务器,通过tcp套接字池传输多个请求(在任何一个时间范围内有数百个请求),tcp套接字通过socks连接连接到N个其他代理客户端。通过传递一个委托(而不是使用异常)也尝试了这种方法,但同样的性能结果很差 正常操作下的日志片段: 14:40:17.700 [PrxSvc:9058] --> [200] 1217ms http://redact.example.com 14
14:40:17.700 [PrxSvc:9058] --> [200] 1217ms http://redact.example.com
14:40:17.700 [PrxSvc:9058] C-DEBUG:C
14:40:17.716 [PrxSvc:9058] --> [200] 1098ms http://redact.example.com
14:40:17.716 [PrxSvc:9058] C-DEBUG:C
14:40:17.727 [PrxSvc:9054] --> [200] 905ms http://redact.example.com
14:40:17.727 [PrxSvc:9054] C-DEBUG:C
14:40:17.778 [PrxSvc:9050] --> [200] 453ms http://redact.example.com
14:40:17.778 [PrxSvc:9050] C-DEBUG:C
14:40:17.781 [Unnamed Thread] C-DEBUG:A
14:40:17.781 [Unnamed Thread] C-DEBUG:B
14:40:17.796 [PrxSvc:9058] --> [200] 652ms http://redact.example.com
14:40:17.796 [PrxSvc:9058] C-DEBUG:C
14:40:17.807 [PrxSvc:9056] --> [200] 1555ms http://redact.example.com
14:40:17.807 [PrxSvc:9056] C-DEBUG:C
14:40:17.816 [PrxSvc:9064] --> [200] 396ms http://redact.example.com
套接字池会恢复到域的连接,但是当外部服务器关闭连接时,我们显然不会收到此通知。当我们尝试通过TcpSocksHandler.TaskHandler方法重用此连接时:
socks.Send(buffer, 0, rcv, SocketFlags.None);
socks.Receive(new byte[1], 0, 1, SocketFlags.Peek);
此异常包含以下内容:
catch
{
//The socket is bad, we should get a new socket.
Log("This socket has expired! - Server has closed the connection.");
Log(String.Format("This socket was {0} seconds old: {1}", seconds, socks.Guid));
socks.Dispose();
Log("C-DEBUG:1");
throw new SocksSocketFailureException(); //throw exception. to bubble back up.
}
然后它的调用代码会捕捉到这一点,在堆栈上多次出现,如下所示:
DoHandleRequest{...
try
{
_actualEndPoint = TcpSocksHandler.TaskHandler(socket, context.SocksSocket, 30000000, method);
}
catch (SocksSocketFailureException ssfe)
{
context.SocksSocket = null;
Logger.Log("C-DEBUG:2");
throw;
}
}
ProxyBase.HandleRequest{...
try
{
...
success = DoHandleRequest(context, out bytes);
}
catch (SocksSocketFailureException ssfex)
{
Logger.Log("C-DEBUG:3");
throw;
}
}
ProxyManager.HandleRequest{
while (true)
{
// Pick the best available proxy to handle the request
Logger.Log("C-DEBUG:A");
IProxy proxy = GetNextProxy(context) ?? NullProxy.Instance;
Logger.Log("C-DEBUG:B");
try
{
// Process the request
proxy.HandleRequest(context);
Logger.Log("C-DEBUG:C");
break;
}
catch(SocksSocketFailureException ssfex)
{
Logger.Log("C-DEBUG:4");
}
catch (Exception)
{
break;
}
}
}
从下面的日志片段中,您可以看到此功能的性能(或缺乏此功能)
没有从上面编辑任何日志行-几乎整秒钟都没有处理任何其他内容!(我们通常可以处理100个请求)。
此外,向堆栈中冒泡一个异常似乎只需要一秒钟(有时甚至更多!)。
-请注意以上日志行的计时。e、 调试:3和调试:4之间的间隔为g..2秒
我不知道是什么原因造成的。如有任何建议/想法,将不胜感激
更新:
基于Eamon的问题,我将相同的x64版本推送到非生产本地机器上,运行64位Windows8。作为服务安装,发布版本,如前一个示例所示。唯一的其他区别是,它现在的目标是4个代理节点(PrxSvc 9050905290549056),而不是之前的80个。
我不能再说这些异常步骤现在是否阻止了线程执行,因为它们是及时执行的:
16:53:59.787 [PrxSvc:9056] This socket has expired! - Server has closed the connection.
16:53:59.787 [PrxSvc:9056] This socket was 0.1280009 seconds old: 69d12cc9-9456-47db-86b2-a2ebf87b41bf
16:53:59.787 [PrxSvc:9056] C-DEBUG:1
16:53:59.787 [PrxSvc:9056] C-DEBUG:2
16:53:59.787 [PrxSvc:9056] C-DEBUG:3
16:53:59.787 [PrxSvc:9056] C-DEBUG:4
16:53:59.787 [PrxSvc:9056] C-DEBUG:A
在这台机器上,代码目前正在以每秒80个请求的速度愉快地处理800个并发请求,并且可以轻松地处理更多。。。
到底是什么导致了这种差异
为了完整性,我用4个节点(而不是80个节点)重新运行了第一个测试(在win2008服务器上),得到了相同的结果:
17:22:44.891 [PrxSvc:9054] C-DEBUG:B
17:22:45.063 [PrxSvc:9054] This socket has expired! - Server has closed the connection.
17:22:45.063 [PrxSvc:9054] This socket was 25.84375 seconds old: cfdee74d-9941-4c8c-80cd-f32aa14b7877
17:22:45.063 [PrxSvc:9054] C-DEBUG:1
17:22:45.485 [PrxSvc:9054] C-DEBUG:2
17:22:45.751 [PrxSvc:9054] C-DEBUG:3
17:22:46.016 [PrxSvc:9054] C-DEBUG:4
异常抛出成本低,捕获成本高。除非代码知道如何处理异常,否则不要捕获异常。C-Debug:2和C-Debug:3似乎就是这样。另外,您还有一个已知条件,即另一端已关闭其连接。您不应该使用异常来处理此问题。您需要公开类似于
IsConnected
的方法或属性,并在循环中检查该方法或属性,而不是依赖于引发的异常:
while(true) {
IProxy proxy = GetNextProxy(context) ?? NullProxy.Instance;
if (!proxy.IsConnected)
continue;
try {
proxy.HandleRequest(context);
break;
} catch {
// handle actual exceptional cases here
}
}
如果需要,您可以向上述代码添加计数器或计时器,并在x次重试次数或特定时间段过期时引发异常,但在任何情况下,断开连接的属性检查都会大大提高代码的性能。这不是您的问题的答案,您的程序中的错误是什么,但是,如果不了解全局或亲自测试您的代码,我很难说出这一点。通常我会为此写评论,但我的文字太长了 您的第一个示例日志(“正常操作”)让我有点困惑。只有一个
C-DEBUG:A
和C-DEBUG:B
,而至少每个C-DEBUG:C
都必须有一个,不是吗
其他的例子看起来不错,一切都如我预期的那样发生(A=>B=>exception=>1=>2=>3=>4=>A)。好吧,似乎只有一条线索,但是例子没有给出这是错误的线索;我没有看到来自另一个线程的第二个C-DEBUG:A
。你期待什么呢
关于您的更新:这里我想了解更多关于您的测试机器的性能,因为每次抛出异常都需要一些执行时间。这就是为什么在循环中抛出异常会对性能造成巨大影响的原因。您的服务器运行时间似乎有点慢,但在我看来还行
虽然无法为您的问题提供提示,但至少我可以告诉您异常引发本身不是其他线程的阻塞因素。以证明我编写了一个小程序(如果您想知道我是如何做到的,请参阅下面的源代码)。该程序的输出是
19:31:09.2788 [Thread-0] 0
19:31:09.2788 [Thread-1] 1
19:31:09.3908 [Thread-0] 0
19:31:09.3908 [Thread-1] 1
19:31:09.4908 [Thread-1] 1
19:31:09.4908 [Thread-0] 0
19:31:09.5908 [Thread-0] 0
19:31:09.5998 [Thread-1] Caught exception callstack frame 29
19:31:09.6078 [Thread-1] Caught exception callstack frame 28
19:31:09.6148 [Thread-1] Caught exception callstack frame 27
19:31:09.6218 [Thread-1] Caught exception callstack frame 26
19:31:09.6288 [Thread-1] Caught exception callstack frame 25
19:31:09.6358 [Thread-1] Caught exception callstack frame 24
19:31:09.6418 [Thread-1] Caught exception callstack frame 23
19:31:09.6488 [Thread-1] Caught exception callstack frame 22
19:31:09.6548 [Thread-1] Caught exception callstack frame 21
19:31:09.6608 [Thread-1] Caught exception callstack frame 20
19:31:09.6668 [Thread-1] Caught exception callstack frame 19
19:31:09.6728 [Thread-1] Caught exception callstack frame 18
19:31:09.6778 [Thread-1] Caught exception callstack frame 17
19:31:09.6828 [Thread-1] Caught exception callstack frame 16
19:31:09.6888 [Thread-1] Caught exception callstack frame 15
19:31:09.6908 [Thread-0] 0
19:31:09.6938 [Thread-1] Caught exception callstack frame 14
19:31:09.6978 [Thread-1] Caught exception callstack frame 13
19:31:09.7028 [Thread-1] Caught exception callstack frame 12
19:31:09.7078 [Thread-1] Caught exception callstack frame 11
19:31:09.7128 [Thread-1] Caught exception callstack frame 10
19:31:09.7168 [Thread-1] Caught exception callstack frame 9
19:31:09.7218 [Thread-1] Caught exception callstack frame 8
19:31:09.7258 [Thread-1] Caught exception callstack frame 7
19:31:09.7299 [Thread-1] Caught exception callstack frame 6
19:31:09.7339 [Thread-1] Caught exception callstack frame 5
19:31:09.7369 [Thread-1] Caught exception callstack frame 4
19:31:09.7409 [Thread-1] Caught exception callstack frame 3
19:31:09.7439 [Thread-1] Caught exception callstack frame 2
19:31:09.7469 [Thread-1] Caught exception callstack frame 1
19:31:09.7499 [Thread-1] Caught exception callstack frame 0
19:31:09.7509 [Thread-1] 1
19:31:09.7919 [Thread-0] 0
19:31:09.8509 [Thread-1] 1
19:31:09.8919 [Thread-0] 0
19:31:09.9509 [Thread-1] 1
19:31:10.0509 [Thread-1] 1
19:31:10.1509 [Thread-1] 1
19:31:10.2509 [Thread-1] 1
19:31:10.3509 [Thread-1] 1
如您所见,thread-0打印一个0
,而thread-1的异常则沿着调用堆栈向上运行。这里禁止阻塞
以下是我的计划供参考:
class Program {
class MyException : Exception {}
// A class for give the starting thread operation some parameters
class ThreadStartParameter {
// For identifying each thread
public int Id { get; set; }
// For building up a deeper callstack frame
public int CallStackDepth { get; set; }
// Indicates that this thread should throw an exception
public bool ThrowException { get; set; }
}
static void Main(string[] args) {
// Create two threads and let them run concurrently
Thread t0 = new Thread(BuildUpCallStack) { Name = "Thread-0" };
Thread t1 = new Thread(BuildUpCallStack) { Name = "Thread-1" };
t0.Start(new ThreadStartParameter {
Id = 0,
CallStackDepth = 0,
ThrowException = false
});
t1.Start(new ThreadStartParameter {
Id = 1,
CallStackDepth = 0,
ThrowException = true
});
Console.Read();
}
// Recursive helper method to build a callstack of 30 frames, which
// is used to rethrow an exception at each level
static void BuildUpCallStack(object data) {
ThreadStartParameter parameter = (ThreadStartParameter)data;
if (parameter.CallStackDepth < 30) {
try {
BuildUpCallStack(new ThreadStartParameter {
Id = parameter.Id,
CallStackDepth = parameter.CallStackDepth + 1,
ThrowException = parameter.ThrowException
});
} catch (MyException e) {
Log(string.Format("Caught exception callstack frame {0}",
parameter.CallStackDepth));
// If an exception occured, rethrow it unless this
// callstack frame was the first
if (parameter.CallStackDepth != 0) throw;
// If this frame was the first in callstack, restart the
// thread but this time without throwing an exception (for
// demonstrate such a restart character like your Proxies do)
BuildUpCallStack(new ThreadStartParameter {
Id = parameter.Id,
CallStackDepth = 0,
ThrowException = false
});
}
return;
}
DoSomething(parameter);
}
static void DoSomething(object data) {
ThreadStartParameter parameter = (ThreadStartParameter)data;
for (int counter = 0; counter < 7; counter++) {
if (counter == 3 && parameter.ThrowException)
throw new MyException();
Log(parameter.Id);
Thread.Sleep(100);
}
}
static void Log(object message) {
Console.WriteLine(
" {0:HH:mm:ss.ffff} [{1}] {2}",
DateTime.Now,
Thread.CurrentThread.Name,
message.ToString());
}
}
类程序{
类MyException:异常{}
//为起始线程操作提供一些参数的类
类线程起始参数{
//用于标识每个线程
公共int Id{get;set;}
//用于构建更深层次的调用堆栈框架
public int CallStackDepth{get;set;}
//指示此线程应引发异常
公共bool ThrowException{get;set;}
}
静态void Main(字符串[]参数){
//创建两个线程并让它们并发运行
Thread t0=新线程(BuildUpCallStack){Name=“Thread-0”};
线程t1=新线程(BuildUpCallStack){Name=“Thread-1”};
t0.开始(新螺纹开始参数{
Id=0,
CallStackDepth=0,
ThroweException=false
});
t1.启动(新螺纹启动参数{
Id=1,
CallStackDepth=0,
ThroweException=true
});
Console.Read();
}
//递归帮助器方法来构建30帧的调用堆栈
//用于在每个级别重新显示异常
静态void BuildUpCallStack(对象数据){
ThreadStartParameter参数=(ThreadStartParameter)数据;
if(parameter.CallStackDepth<3
class Program {
class MyException : Exception {}
// A class for give the starting thread operation some parameters
class ThreadStartParameter {
// For identifying each thread
public int Id { get; set; }
// For building up a deeper callstack frame
public int CallStackDepth { get; set; }
// Indicates that this thread should throw an exception
public bool ThrowException { get; set; }
}
static void Main(string[] args) {
// Create two threads and let them run concurrently
Thread t0 = new Thread(BuildUpCallStack) { Name = "Thread-0" };
Thread t1 = new Thread(BuildUpCallStack) { Name = "Thread-1" };
t0.Start(new ThreadStartParameter {
Id = 0,
CallStackDepth = 0,
ThrowException = false
});
t1.Start(new ThreadStartParameter {
Id = 1,
CallStackDepth = 0,
ThrowException = true
});
Console.Read();
}
// Recursive helper method to build a callstack of 30 frames, which
// is used to rethrow an exception at each level
static void BuildUpCallStack(object data) {
ThreadStartParameter parameter = (ThreadStartParameter)data;
if (parameter.CallStackDepth < 30) {
try {
BuildUpCallStack(new ThreadStartParameter {
Id = parameter.Id,
CallStackDepth = parameter.CallStackDepth + 1,
ThrowException = parameter.ThrowException
});
} catch (MyException e) {
Log(string.Format("Caught exception callstack frame {0}",
parameter.CallStackDepth));
// If an exception occured, rethrow it unless this
// callstack frame was the first
if (parameter.CallStackDepth != 0) throw;
// If this frame was the first in callstack, restart the
// thread but this time without throwing an exception (for
// demonstrate such a restart character like your Proxies do)
BuildUpCallStack(new ThreadStartParameter {
Id = parameter.Id,
CallStackDepth = 0,
ThrowException = false
});
}
return;
}
DoSomething(parameter);
}
static void DoSomething(object data) {
ThreadStartParameter parameter = (ThreadStartParameter)data;
for (int counter = 0; counter < 7; counter++) {
if (counter == 3 && parameter.ThrowException)
throw new MyException();
Log(parameter.Id);
Thread.Sleep(100);
}
}
static void Log(object message) {
Console.WriteLine(
" {0:HH:mm:ss.ffff} [{1}] {2}",
DateTime.Now,
Thread.CurrentThread.Name,
message.ToString());
}
}