新手使用多线程引发的困扰
最近在写个短信服务用于后台发送短信,短信分类有好几只,考虑到发送效率遂采用多线程发送。
短信发送的接口是由供应商提供的,包含了一个示例项目。
通过修改供应商提供的示例,遇到了居然花了较长时间才解决的问题,这问题是我第一次碰到所以记录一下以备后用。
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace SMS
{
/**//// <summary>
/// 概述:引入对动态库dll的几个接口的调用
/// 备注:依赖于 SMS.dll
/// </summary>
public class smsDll
{
const string dllFile = "Lib\\xal.dll"; //请根据文件位置配置此值
[DllImport(dllFile, CharSet = CharSet.Ansi)]
public static extern int Login(string Host, string Port, string Username, string Password);
[DllImport(dllFile, CharSet = CharSet.Ansi)]
public static extern int Send(int ClientID, string Mobiles, string Msg, string ExNum, string AtTime, string ExParam,
StringBuilder ResultValue, ref int ResultSize);
[DllImport(dllFile, CharSet = CharSet.Ansi)]
public static extern int GetProperty(int ClientID, StringBuilder ResultValue, ref int ResultSize);
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace SMS
{
/**//// <summary>
/// 概述:对短信的相关功能简单作了实现封装
/// 备注:依赖于 smsDll.cs
/// </summary>
public class smsWorker:IDisposable
{
private int ClientID = 0;
//属性
public String Host = "123.123.123.123";
public String Port = "8000";
public String Username = "";
public String Password = "";
//方法
/**//* 发送 全参数 */
public int Send(string Mobiles,string Msg,string ExNum,string AtTime,string ExParam,out string ResultStr)
{
ResultStr = null;
if (!_CheckNet()) return -1;
int rtLen = 0;
StringBuilder rtBuf=new StringBuilder(1024);
int rt = 0;
rt = smsDll.Send(ClientID, Mobiles, Msg, ExNum, AtTime, ExParam, rtBuf, ref rtLen);
ResultStr=rtBuf.ToString();
rtBuf.Length=0;
return rt;
}
/**//* 发送 简易 */
public string Send(string Mobiles, string Msg)
{
string Rt = "";
Send(Mobiles, Msg, "123456", "保留字一=1232", "MsgType=71", out Rt);
return Rt;
}
/**//* 获得属性(包括余额信息) */
public int GetProperty(out string ResultStr)
{
ResultStr = null;
if (!_CheckNet()) return -1;
int rtLen = 0;
StringBuilder rtBuf = new StringBuilder(1024);
int rt = smsDll.GetProperty(ClientID, rtBuf, ref rtLen);
ResultStr = rtBuf.ToString();
rtBuf.Length = 0;
return rt;
}
/**//* 登出 */
public void Logout()
{
if (ClientID > 0)
{
smsDll.Logout(ClientID);
ClientID = 0;
}
return;
}
private bool _CheckNet()
{
StringBuilder str=new StringBuilder(1024);
int RS = 0;
int r = smsDll.GetProperty(ClientID, str, ref RS);
if (r!=0)
{
ClientID = smsDll.Login(Host, Port, Username, Password);
}
return ClientID > 0;
}
IDisposable 成员#region IDisposable 成员
public void Dispose()
{
Logout();
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace SMS
{
/**//// <summary>
/// 概述:对短信的相关功能简单作了实现封装
/// 备注:依赖于 smsDll.cs
/// </summary>
public class smsWorker:IDisposable
{
private int ClientID = 0;
//属性
public String Host = "1.1.1.1";
public String Port = "8000";
public String Username = "";
public String Password = "";
/**//* 发送 全参数 */
public int Send(string Mobiles,string Msg,string ExNum,string AtTime,string ExParam,out string ResultStr)
{
ResultStr = null;
if (!_CheckNet()) return -1;
int rtLen = 0;
StringBuilder rtBuf=new StringBuilder(1024);
int rt = 0;
rt = smsDll.Send(ClientID, Mobiles, Msg, ExNum, AtTime, ExParam, rtBuf, ref rtLen);
ResultStr=rtBuf.ToString();
rtBuf.Length=0;
return rt;
}
/**//* 发送 简易 */
public string Send(string Mobiles, string Msg)
{
string Rt = "";
Send(Mobiles, Msg, "123456", "保留字一=1232", "MsgType=71", out Rt);
return Rt;
}
/**//* 登出 */
public void Logout()
{
if (ClientID > 0)
{
smsDll.Logout(ClientID);
ClientID = 0;
}
return;
}
private bool _CheckNet()
{
StringBuilder str=new StringBuilder(1024);
int RS = 0;
int r = smsDll.GetProperty(ClientID, str, ref RS);
if (r!=0)
{
ClientID = smsDll.Login(Host, Port, Username, Password);
}
return ClientID > 0;
}
IDisposable 成员#region IDisposable 成员
public void Dispose()
{
Logout();
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace DLL
{
public partial class FMain : Form
{
SMS.smsWorker sms = new SMS.smsWorker();
public FMain()
{
InitializeComponent();
}
//发送按钮调用事件
private void ToSend(object sender, EventArgs e)
{
SMS.smsWorker sms = new SMS.smsWorker();
for (int i = 0; i < 100; i++)
{
string Rt = sms.Send("13012345678", "测试内容");
}
}
}
}
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DLL
{
public partial class FMain : Form
{
public SMS.smsWorker sms = null;
public FMain()
{
InitializeComponent();
}
//点击发送按钮调用的事件
private void ToSend(object sender, EventArgs e)
{
string Rt= sms.Send(textBox1.Text, textBox2.Text);
if (Rt!=null)
{
MessageBox.Show("成功");
}
else
MessageBox.Show("失败");
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace DLL
{
public partial class FMain : Form
{
SMS.smsWorker sms = new SMS.smsWorker();
public FMain()
{
InitializeComponent();
}
private void ToSend(object sender, EventArgs e)
{
Thread sendA = new Thread(new ThreadStart(SendFunctionA));
sendA.Start();
Thread sendB = new Thread(new ThreadStart(SendFunctionB));
sendB.Start();
}
//发送线程A
private void SendFunctionA()
{
for (int i = 0; i < 100; i++)
{
string Rt = sms.Send("13012345678", "测试内容");
}
}
//发送线程B
private void SendFunctionB()
{
for (int i = 0; i < 100; i++)
{
string Rt = sms.Send("13012345678", "测试内容");
}
}
}
}
启动多线程运行没几秒钟后就抛出了System.AccessViolationException,提示信息为:“尝试读取或写入受保护的内存。这通常指示其他内存已损坏。”
异常出现在接口封装类 _CheckNet函数中的该语句:
int r = smsDll.GetProperty(ClientID, str, ref RS);
原因查N久调试N遍,最后才确认为问题出在多线程访问资源的冲突
然后修改了接口封装类,新增了锁对象,并修改了_CheckNet函数与int Send
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace SMS
{
/**//// <summary>
/// 概述:对短信的相关功能简单作了实现封装
/// 备注:依赖于 smsDll.cs
/// </summary>
public class smsWorker:IDisposable
{
/**//// <summary>
/// 定义锁对象
/// </summary>
object locker = new object();
private int ClientID = 0;
//属性
public String Host = "1.1.1.1";
public String Port = "8000";
public String Username = "";
public String Password = "";
/**//* 发送 全参数 */
public int Send(string Mobiles,string Msg,string ExNum,string AtTime,string ExParam,out string ResultStr)
{
ResultStr = null;
if (!_CheckNet()) return -1;
lock (locker)
{
int rtLen = 0;
StringBuilder rtBuf = new StringBuilder(1024);
int rt = 0;
rt = smsDll.Send(ClientID, Mobiles, Msg, ExNum, AtTime, ExParam, rtBuf, ref rtLen);
ResultStr = rtBuf.ToString();
rtBuf.Length = 0;
}
return rt;
}
/**//* 发送 简易 */
public string Send(string Mobiles, string Msg)
{
string Rt = "";
Send(Mobiles, Msg, "123456", "保留字一=1232", "MsgType=71", out Rt);
return Rt;
}
/**//* 登出 */
public void Logout()
{
if (ClientID > 0)
{
smsDll.Logout(ClientID);
ClientID = 0;
}
return;
}
private bool _CheckNet()
{
lock (locker)
{
StringBuilder str = new StringBuilder(1024);
int RS = 0;
int r = smsDll.GetProperty(ClientID, str, ref RS);
if (r != 0)
{
ClientID = smsDll.Login(Host, Port, Username, Password);
}
}
return ClientID > 0;
}
IDisposable 成员#region IDisposable 成员
public void Dispose()
{
Logout();
}
#endregion
}
}
之后运行程序就没有抛出异常,既然是资源冲突问题,那就不定义公共对象smsWorker,而分别在两个线程里定义smsWorker。运行起来仍然抛出与之前一样的异常,无奈还是改成定义公共对象smsWorker
问题虽然解决了,但问题的核心仍然没有搞明白,线程声明内的smsWorker对象,应该独立的不会出现冲突才是。
难道被调用类的对象只能存在一个?如果大型系统中不慎定义了两个对象,排错岂不是要疯掉了。
看来还应该好好学习一下线程相关的知识。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace DLL
{
public partial class FMain : Form
{
public SMS.smsWorker sms = null;
public FMain()
{
InitializeComponent();
}
private void ToSend(object sender, EventArgs e)
{
string Rt= sms.Send(textBox1.Text, textBox2.Text);
if (Rt!=null)
{
MessageBox.Show("成功");
}
else
MessageBox.Show("失败");
}
}
}
posted on 2009-07-11 12:30 allenjsl 阅读(2413) 评论(16) 编辑 收藏 举报