c#请教多线程同时操作数据库

xiehuangda 2013-10-24 07:32:11
c#请教多线程同时操作数据库

环境:c# WinForm Net2.0 SQL2005

用计时器刷新多个dataGridView取出值,多线程同时操作数据库,SQL有时会抛出各种异常错误。

在同一个WinForm界面,有8个Timer分别刷新8个dataGridView,目的是为了取出各自的Dgv是否有人在线。
然后,同时采用Socket通讯,收到消息时,判断如果有人在线,就操作SQL数据库。
在实际工作中Socket通讯非常频繁,同时又得及时判断是否有人在线,如果没有,不操作SQL,只通讯。如果有人在线还得读写SQL数据库。
这样一来就会发生各种各样的SQL错误,而且不是每次都出错,是不知道什么时候就出错抛出SQL异常。
因为,各个Socket收到各自的消息,就会去读写数据库,读写数据库都在同一个时间内了。而且各个Timer还在一直刷新数据库..撞车的机会非常大

我列出,SQL抛出各种错误异常:
第一种异常:已有打开的与此命令相关联的 DataReader,必须首先将它关闭。
第二种异常:阅读器关闭时 NextResult 的尝试无效。
第三种异常:ExecuteReader 要求已打开且可用的连接。连接的当前状态为已关闭。
第四种异常:ExecuteReader要求已打开且可用的连接.连接的当前状态为正在连接。
第五种异常:内部连接致命错误。
第六种异常:由于目标机器积极拒绝,无法连接。
第七种异常:当前命令发生了严重错误。应放弃任何可能产生的结果
第八种异常:无法运行请求,因为批处理已中止。这可能是由于从客户端发送的中止信号导致的;或者其他请求正在同一会话中运行,这会使会话处于忙状态。
第九种异常:从服务器接收结果时发生传输级错误。 (provider: SMux 提供程序, error: 20 - 连接已关闭。)
第十种异常:从服务器接收结果时发生传输级错误。 (provider: 会话提供程序, error: 19 - 物理连接不可用)


终究的问题就是,在多线程中同时操作数据库发生冲突了,请教各位有什么好方法解决!谢谢!

private DataSet ds1 = new DataSet();//设置为ds
private DataSet ds2 = new DataSet();//设置为ds
private SqlDataAdapter da1;//设置为da
private SqlDataAdapter da2;//设置为da
//...ds3 4 5 6.. da3 4 5 6....

String ZU1_h1,ZU1_h2,ZU1_h3,ZU1_h4,ZU1_h5,ZU1_h6;//取出dataGridView1某列N行的值
String ZU2_h1,ZU2_h2,ZU2_h3,ZU2_h4,ZU2_h5,ZU2_h6;//取出dataGridView2某列N行的值
//.......


Socket Sokt1;//定义一个Socket类的对象
Socket Sokt2;//定义一个Socket类的对象
//.....

Thread th1;//定义一个Thread类的对象,进程
Thread th2;//定义一个Thread类的对象,进程
//.....


在同一个WinForm中有8个 dataGridView

private void FormMain_Load(object sender, EventArgs e)
{
try
{
string sql1 = "Select * From biao Where Crew=1 order by Code asc";
da1 = new SqlDataAdapter(sql1, Conn.rs);
da1.Fill(ds1, "ZU1");
Dgv1.DataSource = ds1.Tables["ZU1"];
}
catch (Exception excha1)
{
//..
}
finally
{
Conn.rs.Close();
}



Thread.Sleep(50);

try
{
string sql2 = "Select * From biao Where Crew=2 order by Code asc";
da2 = new SqlDataAdapter(sql2, Conn.rs);
da2.Fill(ds2, "ZU2");
Dgv2.DataSource = ds2.Tables["ZU2"];
}
catch (Exception excha2)
{
//..
}
finally
{
Conn.rs.Close();
}

//sql3 4 5 6.... ZU3 4 5 6...

}


//用计时器Timer1,定时4秒刷新dataGridView1
private void Timer1_Tick(object sender, EventArgs e)
{
ds1.Tables["ZU1"].Clear();
da1.Fill(ds1, "ZU1");

ZU1_h1 = Dgv1.Rows[0].Cells["Code1"].Value.ToString().Trim();//Dgv第一行
ZU1_h2 = Dgv1.Rows[1].Cells["Code1"].Value.ToString().Trim();//Dgv第二行
ZU1_h3 = Dgv1.Rows[2].Cells["Code1"].Value.ToString().Trim();//Dgv第三行
ZU1_h4 = Dgv1.Rows[3].Cells["Code1"].Value.ToString().Trim();//Dgv第四行
ZU1_h5 = Dgv1.Rows[4].Cells["Code1"].Value.ToString().Trim();//Dgv第五行
ZU1_h6 = Dgv1.Rows[5].Cells["Code1"].Value.ToString().Trim();//Dgv第六行
}

//用计时器Timer2,定时5秒刷新dataGridView2
private void Timer2_Tick(object sender, EventArgs e)
{
ds2.Tables["ZU2"].Clear();
da2.Fill(ds2, "ZU2");
ZU2_h1 = Dgv2.Rows[0].Cells["Code2"].Value.ToString().Trim();//Dgv第一行
ZU2_h2 = Dgv2.Rows[1].Cells["Code2"].Value.ToString().Trim();//Dgv第二行
ZU2_h3 = Dgv2.Rows[2].Cells["Code2"].Value.ToString().Trim();//Dgv第三行
ZU2_h4 = Dgv2.Rows[3].Cells["Code2"].Value.ToString().Trim();//Dgv第四行
ZU2_h5 = Dgv2.Rows[4].Cells["Code2"].Value.ToString().Trim();//Dgv第五行
ZU2_h6 = Dgv2.Rows[5].Cells["Code2"].Value.ToString().Trim();//Dgv第六行
}
//定时刷新...共8个dataGridView



//定义多线程1
private void BeginListen1()//接收发送连接
{
IPAddress ip = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];//获取本机IP
IPEndPoint ipe = new IPEndPoint(ip, 10000);//实例化一个服务器端指定端口的IPEndPoint
Sokt1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建一个套接字的TCP/IP

try
{
Sokt1.Bind(ipe);//将套接字绑定到本地端点
Sokt1.Listen(390);//侦听传入的连接,允许最大连接数

//循环接收并处理数据
while (true)
{
Socket skt = Sokt1.Accept();//在等待一个传入的连接
skt.ReceiveTimeout = 6000;//接收超时,n毫秒
Byte[] by = new Byte[80];//定义接收字节数组,接收长度
Int32 byRec = skt.Receive(by);//接收数据int
String data = Encoding.ASCII.GetString(by, 0, byRec);//收到的数据

//取值
String Hang1 = ZU1_h1;
String Hang1 = ZU1_h2;
String Hang1 = ZU1_h3;
String Hang1 = ZU1_h4;
String Hang1 = ZU1_h5;
String Hang1 = ZU1_h6;

if (Hang1.Length != 0)//Dgv第一行,有人在线
{
try
{
Int32 result = 0;
string sql = string.Format("Update Abiao Set Code='" + ZU1_h1 + "' Where ID=1");
SqlCommand cmd = new SqlCommand(sql, Conn.rs);
Conn.rs.Open();
result = cmd.ExecuteNonQuery();
if (result < 1)
{
new SqlCommand("Update Bbiao Set Code='' Where BLie='" + ZU1_h1 + "'", Conn.rs).ExecuteNonQuery();
}
else
{
new SqlCommand("Insert Into Cbiao (Code)values('" + ZU1_h1 + "')", Conn.rs).ExecuteNonQuery();
}
}
catch (Exception ex)
{
//...
}
finally
{
Conn.rs.Close();
}
}
Thread.Sleep(15);//多线程延迟N毫秒
skt.Shutdown(SocketShutdown.Both);//发送和接收,二者都结束
skt.Close();//关闭连接
}
}
catch (Exception ex)
{
//...
}
}

//定义多线程2
private void BeginListen2()//接收发送连接
{
//......
}

//定义多线程2 4 5 6......


//点击按钮启动线程1
private void btnStart1_Click(object sender, EventArgs e)
{
th1 = new Thread(new ThreadStart(BeginListen1));//创建一个新的线程专门用于处理监听
th1.IsBackground = true;
th1.Start();//启动线程
}


//点击按钮启动线程2
private void btnStart2_Click(object sender, EventArgs e)
{
th2 = new Thread(new ThreadStart(BeginListen2));//创建一个新的线程专门用于处理监听
th2.IsBackground = true;
th2.Start();//启动线程
}

//点击按钮启动线程2 4 5 6.......


请教各位有什么好方法解决多线程同时操作数据库导致各种异常问题,代码尽量写详细点,谢谢!
...全文
4064 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
张金富 2016-01-09
  • 打赏
  • 举报
回复
我也遇得到类似的问题 我想单独创建一个线程或者程序进行数据库操作 但是不知道如何通讯 还有需要数据库查询的操作 没办法独立吧
ericmpdeng 2014-12-17
  • 打赏
  • 举报
回复
你好,你的问题解决了么,我也碰到了类似的问题!
wql860929645 2014-08-28
  • 打赏
  • 举报
回复
其实没有SP1234说的那么严重,共享数据连接是可以的,只不过你每条线程下控制一个共享,一般就没问题,而且还大量减少创建连接之类的时间。
  • 打赏
  • 举报
回复
还有你还是没理解楼上的意思,每次访问数据库创建一次连接语句: 你最多就是传递一个非静态的sql连接字符串变量吧,范例:

            OracleConnection conn = null;
            DataTable dataTable = null;
            try
            {
                conn = new OracleConnection(conStr);
                conn.Open();
                OracleCommand cmd = new OracleCommand();
                cmd.CommandText = sqlText;
                cmd.Connection = conn;
                if (parms != null && parms.Count > 0)
                {
                    cmd.Parameters.AddRange(parms.ToArray());
                }
                OracleDataAdapter oda = new OracleDataAdapter(cmd);
                DataSet dataSet = new DataSet();
                oda.Fill(dataSet);
                dataTable = dataSet.Tables[0];
            }
            catch (Exception ex)
            {
                log.Error(sqlText);
                log.Error(ex.Message);
                throw ex;
            }
            finally
            {
                conn.Close();
            }
  • 打赏
  • 举报
回复
引用 9 楼 xiehuangda 的回复:
我的Conn.cs文件中有New,不知道,是否可以。
class Conn
    {
        private static string cn = "Data Source=127.0.0.1;Initial Catalog=Db2013;User ID=sa;pwd=123456;MultipleActiveResultSets=True";
        public static SqlConnection rs = new SqlConnection(cn);
    }
请各位指导。谢谢。
多线程不要使用静态变量,尽量使用实例化。
xiehuangda 2013-10-31
  • 打赏
  • 举报
回复
感谢sp1234 指导,就是代码不够详细 还有高手在吗?请写代码详细点,谢谢!
xiehuangda 2013-10-31
  • 打赏
  • 举报
回复
请问,我在窗体加载的时候,怎么样写。让数据显示到dataGridView中 然后,每N秒计时器里刷新又怎么样写。谢谢
xiehuangda 2013-10-29
  • 打赏
  • 举报
回复
我的Conn.cs文件中有New,不知道,是否可以。
class Conn
    {
        private static string cn = "Data Source=127.0.0.1;Initial Catalog=Db2013;User ID=sa;pwd=123456;MultipleActiveResultSets=True";
        public static SqlConnection rs = new SqlConnection(cn);
    }
请各位指导。谢谢。
  • 打赏
  • 举报
回复 1
别说是多线程访问,就算是单线程访问,例如你的某个读取数据库循环语句中调用了另外一个功能,这个功能也需要读取数据,那么就会出现“已有打开的与此命令相关联的 DataReader,必须首先将它关闭”的错误。 而你的代码不但有这类设计错误,并且在调用 Conn.rs.Open()和 Conn.rs.Close() 上存在着胡乱穿插随机调用的问题。这说明你没有推敲、用头脑“模拟演练”过程程序调用的复杂过程。 回到第一个编程知识,(至少在使用SQL Server的ADO.Net引擎驱动时)刚入门的asp.net程序员也懂得“使用DbConnection必须单独创建、使用完即时关闭”的。除非你漠视这种警告,而简单地抄写早些年某些人写的垃圾博客上的代码,否则应该不会写出那样的代码。
  • 打赏
  • 举报
回复
你的所谓 Conn.rs 这是个“老鼠屎”,这是一个严重的设计BUG。 在你的代码中应该new一个新的连接对象,例如
                    if (Hang1.Length != 0)//Dgv第一行,有人在线
                    {
                        try
                        {
                          using(var conn = CreateNewConnection())
                          {
                            conn.Open();
                            Int32 result = 0;
                            string sql = string.Format("Update Abiao Set Code='" + ZU1_h1 + "' Where ID=1");
                            SqlCommand cmd = new SqlCommand(sql, conn);
                            result = cmd.ExecuteNonQuery();
                            if (result < 1)
                            {
                                new SqlCommand("Update Bbiao Set Code='' Where BLie='" + ZU1_h1 + "'", conn).ExecuteNonQuery();
                            }
                            else
                            {
                                new SqlCommand("Insert Into Cbiao (Code)values('" + ZU1_h1 + "')", conn).ExecuteNonQuery();
                            }
                          }
                        }
                        catch (Exception ex)
                        {
                            //...
                        }
                    }
需要在局部中临时创建数据库连接,然后访问完,尽快释放(在using结束会自动释放)。 其它地方也是一样。不是搞什么“共享DbConnection”,那种以为“只要创建一次数据库逻辑连接然后就能到处访问”的做法就会造成你这一堆BUG。
xiehuangda 2013-10-26
  • 打赏
  • 举报
回复
请教,是否有高手,能有好方法解决这个问题。 请写代码详细点,谢谢!
火拼阿三 2013-10-24
  • 打赏
  • 举报
回复
这涉及到的问题太多,比如楼上说的你是否关闭了连接,你是否同步操作数据库,防止并发,又或者你的端口被占用,或者你开关连接数据库太快,导致数据库不能及时打开。。我建议LZ好好看下代码和理解一些深层的基础。。
zmn0079 2013-10-24
  • 打赏
  • 举报
回复
只要数据库支持,用多个连接比较简单
  • 打赏
  • 举报
回复
跳出你这个问题,来强调一下编程之道:你编写多线程程序来测试出你的底层代码有BUG,这样才能发现自己的基础代码(例如SQLHelper)里的一大堆设计错误。应该庆幸发现得足够早。
  • 打赏
  • 举报
回复
多线程是很常见的。例如编写那种asp.net程序的人,程序部署到生产服务器以后,asp.net系统处理每一个消息请求都是在线程中的,同一时间可能有并发处理几十个请求。那么那种搞什么“共享DbConnection”的代码,就只能是你自己单机玩玩儿而已,在多并发下肯定要出现“已有打开的与此命令相关联的 DataReader,必须首先将它关闭”这类BUG了。 后边的BUG我懒的看了。因为第一个问题已经说明,你没有采取正确的ADO.NET并发访问下进行编程的方法。进行ADO.NET编程,类似于
            List<MyObj> result;
              using (var conn = new OracleConnection(cnStr))
              {
                  conn.Open();
                  var comm = conn.CreateCommand();
                  comm.CommandText = "select e.TheName, e.Number from Users as e where ..........";
                  comm.CommandType = System.Data.CommandType.Text;
                  result = ( from DbDataRecord record in comm.ExecuteReader()                             select new MyObj{ 
                                              Name = (string)record["TheName"],
                                              Value = (double)record["Number"]
                                         };
                           ).ToList();
              }   
这样来返回 List<T> 对象集合,保证及时关闭 conn,同时也绝不向外界抛出 DataReader。
xiehuangda 2013-10-24
  • 打赏
  • 举报
回复
访问读写数据库非常频繁,而且是多用户,多线程。所以,经常撞车了。请教各位有什么好的解决方法。 请写代码详细点。谢谢!

110,531

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧