这个共享的 DbCommand 对象线程安全吗?
我不明白为什么每次需要调用存储过程时都必须创建 DbCommand 对象。所以我正在尝试想出一种方法来做到这一点。我已经测试了我的代码(见下文)。但我想向社区核实一下,以防我遗漏了什么。我将在 ASP.NET 应用程序中使用它。这段代码线程安全吗?
SharedDbCommand - 包装 DbCommand 对象的创建和存储
Db - 数据库的包装器,通过静态字段和 ThreadStatic 属性使用 SharedDbCommand
Program - 启动线程并使用 Db 对象的控制台应用程序
// SharedDbCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data;
namespace TestCmdPrepare {
public class SharedDbCommand {
[ThreadStatic]
static DbCommand cmd;
public SharedDbCommand(string procedureName, ConnectionStringSettings dbConfig) {
var factory = DbProviderFactories.GetFactory(dbConfig.ProviderName);
cmd = factory.CreateCommand();
cmd.Connection = factory.CreateConnection();
cmd.Connection.ConnectionString = dbConfig.ConnectionString;
cmd.CommandText = procedureName;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
if (cmd is SqlCommand) {
try {
cmd.Connection.Open();
SqlCommandBuilder.DeriveParameters(cmd as SqlCommand);
} finally {
if (cmd != null && cmd.Connection != null)
cmd.Connection.Close();
}
}
}
public DbParameter this[string name] {
get {
return cmd.Parameters[name];
}
}
public IDataReader ExecuteReader() {
try {
cmd.Connection.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
} finally {
cmd.Connection.Close();
}
}
public void ExecuteNonQuery() {
try {
cmd.Connection.Open();
cmd.ExecuteNonQuery();
} finally {
cmd.Connection.Close();
}
}
}
}
// Db.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using System.Diagnostics;
namespace TestCmdPrepare {
public class Db {
ConnectionStringSettings dbSettings;
DbProviderFactory factory;
public Db() {
dbSettings = ConfigurationManager.ConnectionStrings["db"];
factory = DbProviderFactories.GetFactory(dbSettings.ProviderName);
}
IDataReader ExecuteReader(DbCommand cmd) {
cmd.Connection.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
private DbConnection CreateConnection() {
var c = factory.CreateConnection();
c.ConnectionString = dbSettings.ConnectionString;
return c;
}
DbCommand CreateCommand(string procedureName) {
var cmd = factory.CreateCommand();
cmd.Connection = CreateConnection();
cmd.CommandText = "get_stuff";
cmd.CommandType = CommandType.StoredProcedure;
if (cmd is SqlCommand) {
try {
cmd.Connection.Open();
SqlCommandBuilder.DeriveParameters(cmd as SqlCommand);
} finally {
cmd.Connection.Close();
}
}
return cmd;
}
[ThreadStatic]
static DbCommand get_stuff;
DbCommand GetStuffCmd {
get {
if (get_stuff == null)
get_stuff = CreateCommand("get_stuff");
return get_stuff;
}
}
public string GetStuff(int id) {
GetStuffCmd.Parameters["@id"].Value = id;
using (var reader = ExecuteReader(GetStuffCmd)) {
if (reader.Read()) {
return reader.GetString(reader.GetOrdinal("bar"));
}
}
return null;
}
[ThreadStatic]
static SharedDbCommand get_stuff2;
public string GetStuff2(int id) {
if (get_stuff2 == null)
get_stuff2 = new SharedDbCommand("get_stuff", dbSettings);
get_stuff2["@id"].Value = id;
using (var reader = get_stuff2.ExecuteReader()) {
if (reader.Read()) {
Thread.Sleep(1000);
return reader.GetString(reader.GetOrdinal("bar"));
}
}
return null;
}
}
}
// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Configuration;
using System.Data.SqlClient;
using System.Threading;
namespace TestCmdPrepare {
class Program {
static void Main(string[] args) {
var db = new Db();
var threads = new List<Thread>();
for (int i = 0; i < 4; i++) {
var one = new Thread(Run2);
var two = new Thread(Run1);
threads.Add(one);
threads.Add(two);
one.Start();
two.Start();
Write(db, 1);
Write(db, 2);
}
Console.WriteLine("Joining");
foreach (var thread in threads) {
thread.Join();
}
Console.WriteLine();
Console.WriteLine("DONE");
Console.ReadLine();
return;
}
static void Write(Db db, int id) {
Console.WriteLine("2:{0}:{1}", Thread.CurrentThread.ManagedThreadId, db.GetStuff2(id));
Console.WriteLine("1:{0}:{1}", Thread.CurrentThread.ManagedThreadId, db.GetStuff(id));
}
static void Run1() {
var db = new Db();
Write(db, 1);
}
static void Run2() {
var db = new Db();
Write(db, 2);
}
}
}
I don't see why I have to create a DbCommand object every time I need to call a stored procedure. So I'm trying to come up with a way to do that. I have tested my code (see below). But I would like to check with the community in case there is something I have missed. I would be using it with in an ASP.NET app. Is this code thread safe?
SharedDbCommand - wraps up the creation and storage of the DbCommand object
Db - the wrapper for the database, uses the SharedDbCommand via a static field and the ThreadStatic attribute
Program - the console app that starts threads and uses the Db object which
// SharedDbCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data;
namespace TestCmdPrepare {
public class SharedDbCommand {
[ThreadStatic]
static DbCommand cmd;
public SharedDbCommand(string procedureName, ConnectionStringSettings dbConfig) {
var factory = DbProviderFactories.GetFactory(dbConfig.ProviderName);
cmd = factory.CreateCommand();
cmd.Connection = factory.CreateConnection();
cmd.Connection.ConnectionString = dbConfig.ConnectionString;
cmd.CommandText = procedureName;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
if (cmd is SqlCommand) {
try {
cmd.Connection.Open();
SqlCommandBuilder.DeriveParameters(cmd as SqlCommand);
} finally {
if (cmd != null && cmd.Connection != null)
cmd.Connection.Close();
}
}
}
public DbParameter this[string name] {
get {
return cmd.Parameters[name];
}
}
public IDataReader ExecuteReader() {
try {
cmd.Connection.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
} finally {
cmd.Connection.Close();
}
}
public void ExecuteNonQuery() {
try {
cmd.Connection.Open();
cmd.ExecuteNonQuery();
} finally {
cmd.Connection.Close();
}
}
}
}
// Db.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.Common;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using System.Diagnostics;
namespace TestCmdPrepare {
public class Db {
ConnectionStringSettings dbSettings;
DbProviderFactory factory;
public Db() {
dbSettings = ConfigurationManager.ConnectionStrings["db"];
factory = DbProviderFactories.GetFactory(dbSettings.ProviderName);
}
IDataReader ExecuteReader(DbCommand cmd) {
cmd.Connection.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
private DbConnection CreateConnection() {
var c = factory.CreateConnection();
c.ConnectionString = dbSettings.ConnectionString;
return c;
}
DbCommand CreateCommand(string procedureName) {
var cmd = factory.CreateCommand();
cmd.Connection = CreateConnection();
cmd.CommandText = "get_stuff";
cmd.CommandType = CommandType.StoredProcedure;
if (cmd is SqlCommand) {
try {
cmd.Connection.Open();
SqlCommandBuilder.DeriveParameters(cmd as SqlCommand);
} finally {
cmd.Connection.Close();
}
}
return cmd;
}
[ThreadStatic]
static DbCommand get_stuff;
DbCommand GetStuffCmd {
get {
if (get_stuff == null)
get_stuff = CreateCommand("get_stuff");
return get_stuff;
}
}
public string GetStuff(int id) {
GetStuffCmd.Parameters["@id"].Value = id;
using (var reader = ExecuteReader(GetStuffCmd)) {
if (reader.Read()) {
return reader.GetString(reader.GetOrdinal("bar"));
}
}
return null;
}
[ThreadStatic]
static SharedDbCommand get_stuff2;
public string GetStuff2(int id) {
if (get_stuff2 == null)
get_stuff2 = new SharedDbCommand("get_stuff", dbSettings);
get_stuff2["@id"].Value = id;
using (var reader = get_stuff2.ExecuteReader()) {
if (reader.Read()) {
Thread.Sleep(1000);
return reader.GetString(reader.GetOrdinal("bar"));
}
}
return null;
}
}
}
// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Configuration;
using System.Data.SqlClient;
using System.Threading;
namespace TestCmdPrepare {
class Program {
static void Main(string[] args) {
var db = new Db();
var threads = new List<Thread>();
for (int i = 0; i < 4; i++) {
var one = new Thread(Run2);
var two = new Thread(Run1);
threads.Add(one);
threads.Add(two);
one.Start();
two.Start();
Write(db, 1);
Write(db, 2);
}
Console.WriteLine("Joining");
foreach (var thread in threads) {
thread.Join();
}
Console.WriteLine();
Console.WriteLine("DONE");
Console.ReadLine();
return;
}
static void Write(Db db, int id) {
Console.WriteLine("2:{0}:{1}", Thread.CurrentThread.ManagedThreadId, db.GetStuff2(id));
Console.WriteLine("1:{0}:{1}", Thread.CurrentThread.ManagedThreadId, db.GetStuff(id));
}
static void Run1() {
var db = new Db();
Write(db, 1);
}
static void Run2() {
var db = new Db();
Write(db, 2);
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
出于多种原因,这是个坏主意。其他人已经提到了其中的一些,但我将给您一个特定于您的实现的方法:在 ASP.NET 中使用 ThreadStatic 最终会咬住您(请参阅 此处)。您无法控制管道,因此多个线程可能最终为一个请求提供服务(想想页面上的事件处理程序等 - 有多少代码正在运行不属于您的代码?) 。由于未处理的异常,也很容易在不属于您的请求线程上孤立数据。可能不是一个令人震惊的问题,但最好的情况是,当你的连接只是坐在那里时,你会看到内存泄漏和服务器资源使用增加......
我建议你让 ConnectionPool 完成它的工作 - 它有一些缺点,但与 ASP.NET 管道中发生的其他事情相比,性能并不是其中之一。如果您确实想这样做,至少考虑将连接对象存储在 HttpContext.Current.Items 中。您可能可以在有限的情况下完成这项工作,但总会遇到问题,特别是当您开始编写并行代码时。
只需从去过那里的人那里得到 0.02 美元。 :)
Bad idea for lots of reasons. Others have mentioned some of them, but I'll give you one specific to your implementation: using ThreadStatic in ASP.NET will bite you eventually (see here). You don't control the pipeline, so it's possible for multiple threads to end up servicing one request (think between event handlers on a page, etc- how much code is running that isn't yours?). It's also easy to orphan data on a request thread you don't own due to unhandled exceptions. Might not be a showstopping problem, but best case you're looking at a memory leak and increased server resource usage while your connection just sits there...
I'd recommend you just let the ConnectionPool do its job- it has some warts, but performance isn't one of them compared to what else is going on in the ASP.NET pipeline. If you really want to do this, at least consider storing the connection object in HttpContext.Current.Items. You can probably make this work in limited circumstances, but there will always be gotchas, especially if you ever start writing parallel code.
Just $0.02 from a guy who's been there. :)
这不是保持 DbCommand 创建状态的好习惯。此外,由于线程处理逻辑,这使得您的应用程序变得非常复杂。
正如 Microsoft 建议的那样,您应该在执行查询后立即创建并处置连接和命令对象。它们的创建非常轻量。无需保留它们所使用的内存 - 当查询执行完成并且已获取所有结果时处置它们。
This is not a good practice to keep DbCommand created. Also, this makes your application very complex because of thread handling logic.
As Microsoft suggests you should create and dispose connection and command objects right after you have executed your query. They are very lightweight to create. No need to keep memory used with them - dispose them when your query execution is complete and you have fetched all of the results.