好的,这看起来像.NET中的一个主要基本错误:
考虑以下简单程序,它故意尝试连接到不存在的数据库:
class Program
{
static void Main(string[] args)
{
Thread threadOne = new Thread(GetConnectionOne);
Thread threadTwo = new Thread(GetConnectionTwo);
threadOne.Start();
threadTwo.Start();
}
static void GetConnectionOne()
{
try
{
using (SqlConnection conn = new SqlConnection("Data Source=.\\wfea;Initial Catalog=zc;Persist Security Info=True;Trusted_Connection=yes;"))
{
conn.Open();
}
} catch (Exception e)
{
File.AppendAllText("ConnectionOneError.txt", e.Message + "\n" + e.StackTrace + "\n");
}
}
static void GetConnectionTwo()
{
try
{
using (SqlConnection conn = new SqlConnection("Data Source=.\\wfea;Initial Catalog=zc;Persist Security Info=True;Trusted_Connection=yes;"))
{
conn.Open();
}
}
catch (Exception e)
{
File.AppendAllText("ConnectionTwoError.txt", e.Message + "\n" + e.StackTrace + "\n");
}
}
}
运行此程序并在catch块上设置断点。 DBConnection对象将尝试连接15秒(在两个线程上),然后它将引发错误。检查异常的堆栈跟踪,堆栈跟踪将混合两个调用堆栈,如下所示:
at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject)
at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject)
at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
at System.Data.SqlClient.SqlConnection.Open()
at ZoCom2Test.Program.GetConnectionOne() in C:\src\trunk\ZTest\Program.cs:line 38
at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
at System.Data.SqlClient.SqlConnection.Open()
at ZoCom2Test.Program.GetConnectionTwo() in C:\src\trunk\ZTest\Program.cs:line 54
你可能需要多次尝试才能实现这一点,但我现在正在机器上实现这一点。这怎么可能?这在VM级别应该是完全不可能的。看起来DBConnection.Open()函数同时在两个线程上抛出相同的异常,或类似的东西。
答案 0 :(得分:6)
试试这个,看看会发生什么:
class ThreadingBug
{
private const string CONNECTION_STRING =
"Data Source=.\\wfea;Initial Catalog=catalog;Persist Security Info=True;Trusted_Connection=yes;";
static void Main(string[] args)
{
try
{
Thread threadOne = new Thread(GetConnectionOne);
Thread threadTwo = new Thread(GetConnectionTwo);
threadOne.Start();
threadTwo.Start();
threadOne.Join(2000);
threadTwo.Join(2000);
}
catch (Exception e)
{
File.AppendAllText("Main.txt", e.ToString());
}
}
static void GetConnectionOne()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
File.AppendAllText("GetConnectionOne.txt", e.ToString());
}
}
static void GetConnectionTwo()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
File.AppendAllText("GetConnectionTwo.txt", e.ToString());
}
}
}
我相信这里有一个错误,虽然它既不是主要的,也不是根本的。在努力缩小范围之后(以及执行诸如删除一个线程之类的操作)之后,看起来两个线程上的连接池实现都抛出了Exception
类的相同实例(为了发现这一点而感谢Gregory)。这有时会显示为损坏的(“混合”)堆栈跟踪,有时简单地表示两个线程上的堆栈跟踪相同,即使代码在两个线程之间完全不同。
注释掉其中一个Thread.Start
调用显示完全不同的堆栈跟踪,证明奇数部分位于连接池实现中 - 奇数堆栈跟踪由连接池分发,因为两个线程都使用相同的连接字符串和凭据。
我已在https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=522506处就此问题提交了Connect问题。每个人都可以自由地投票决定你觉得它有多重要(或不重要),你是否可以重现它,或者你是否有一个解决方法。这将有助于Microsoft确定修复的优先级。
更新:Connect问题已更新。微软承认这是一个错误,并计划在未来版本中修复它。
感谢nganju,Gregory和其他所有参与解决此问题的人。这确实是一个错误,它将被修复,这是因为我们。
答案 1 :(得分:3)
好的,我设法在和(*)调试器内重现了这个(VS2008,FX3.5SP1,双核)。在改变了捕获逻辑之后,它甚至可以可靠地重现。而且,就像Gregory提到的那样,它是两个线程中抛出的相同的异常实例。
这应该是完全不可能的 VM级别。
你从哪里得到这个想法?
两个线程都试图通过连接池进行连接。我不知道Pool如何工作,但我会猜测:它正在序列化2个同时发出的请求。这听起来对服务器很好。然后当尝试失败时,它有1个异常和2个等待线程。
我也希望CLR或ConnectionPool复制异常并预先添加2个独立的堆栈跟踪,但它会合并2个调用跟踪。
所以我认为您的错误很可能是功能,状态:按设计。
因为它不是真正的“混合”堆栈跟踪,而是更多故意Y形的堆栈跟踪。这看起来不是偶然的。
如果有人找到了这种行为的参考,那就太好了。现在我不确定这是CLR还是ConnectionPool'功能'。
(*)编辑:我想我曾经在调试器外面看过它,但现在我无法重现它。所以它可能是调试器或计时问题。
答案 2 :(得分:3)
这不是VM中的错误。这是你的违规行:
private static readonly DbConnectionFactory _connectionFactory;
我们有内部连接池。其中存储了对发生的异常的引用。 这会在执行多线程时打开竞争条件。
我们如何证明这一点?
逻辑上,如果您使用不同的连接池,那么我们将不会遇到此竞争条件。因此,我使用每个线程的连接字符串中指定的不同数据源重新进行相同的测试。现在可以正确显示例外情况。
这实际上是连接池不是线程安全的情况。
答案 3 :(得分:2)
您将获得相同的异常抛出。但是,我不明白为什么。看一下输出窗口,特别是exception1 == exception2。
class ThreadingBug
{
private const string CONNECTION_STRING =
"Data Source=.\\wfea;Initial Catalog=catalog;Persist Security Info=True;Trusted_Connection=yes;";
static void Main(string[] args)
{
try
{
Thread threadOne = new Thread(GetConnectionOne);
Thread threadTwo = new Thread(GetConnectionTwo);
threadOne.Start();
threadTwo.Start();
threadOne.Join(20000);
threadTwo.Join(20000);
Debug.WriteLine("Same?" + (exception1 == exception2));
}
catch (Exception e)
{
Debug.WriteLine("error main" + e);
}
}
static Exception exception1;
static void GetConnectionOne()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
Debug.WriteLine("Error Con one" + e);
exception1 = e;
}
}
static Exception exception2;
static void GetConnectionTwo()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
Debug.WriteLine("Error Con two" + e);
exception2 = e;
}
}
}
编辑:以下是我原来的回复。
你的“随机”文件名很可能是相似的,如果不相同的话,有时会在非常接近的时间范围内调用它们。通常,当您遇到随机出现的问题,并且您有一个Random.Next调用时,它应该是您首先看到的地方。