关于delphi下操作线程、处理并发

mengdiewufeng 2011-12-28 04:34:27
前段时间从辞职的同事那接了一个餐饮消费的项目,硬件商有提供相应的接口,接口中有包含相应的事件,比如消费事件,比如查询余额事件,根据这些事件去和餐饮机完成交互,目前采用的方式是程序打开的时候,把相关设置(就餐时段、卡类、员工信息、就餐环境等等)保存到本地的表格,再使用一个TStringList加载关键字段,比如,我把员工信息保存到一个StringGrid中,然后把员工ID添加到对应的TStringList中,到时候直接根据TStringList获取到该员工在表格的对应行,获取其其它的信息,比如所属卡类等等,消费的时候经过一系列判断,都通过的话,把消费记录保存到本地文本,固定时间批量上传,目前遇到问题是,就餐的时候,并发数可能达到四、五十个,然后服务端运行的时候,有时候会爆出地址错误,我在事件下用try except都拦截不下来,它会直接弹出,后有打电话像硬件提供商咨询,他们说,要使用线程锁什么的,鉴于本人在线程方面的菜鸟水平,故有以下问题咨询:
1、线程中,是否可以有多函数(过程),比如我想在消费事件执行线程的某一个函数或者过程,在查询余额的时候执行另一个函数或者过程,
2、执行线程的时候,能否返回值,比如返回错误代码,以便于和设备的交互
3、线程锁,是怎么锁?
以上问题,可能会幼稚了点,但是盼望得到解答,谢谢
...全文
1859 41 打赏 收藏 转发到动态 举报
写回复
用AI写文章
41 条回复
切换为时间正序
请发表友善的回复…
发表回复
erhan 2011-12-30
  • 打赏
  • 举报
回复
你好,临界区一要防死锁(这是必须的),二要尽量减少区内代码及提高其运行效率(只要不是大循环,现在的机器性能,对此要求已不重要,但一个良好的开发习惯是重要的)。

如果,你那个事件一进入的时候进到临界区里,事件里创建的线程想进也进不去了,又退不出来,这就是死锁了。
临界区 应该 且 只应该 加在需要互斥的地方,领会并记牢。
mengdiewufeng 2011-12-30
  • 打赏
  • 举报
回复
[Quote=引用 38 楼 erhan 的回复:]

其实我从没用过WaitForSingleObject这玩意,呵呵。

线程等结束,我一直用的是我写的那段代码。

没研究过那个,说不出差别来。
[/Quote]
你好,我现在消费事件是这样写的,

procedure TFMainServer.CRCLANX1Consume(ASender: TObject; const AddrLan,
CardNo: WideString; Mode: Integer; const Data: WideString;
var CanContinue: WordBool; var CardBalance, SubsidyFund: Single;
var IsVerifyPassword: WordBool; var Password: WideString;
var ErrorCode: Integer);
var
XFSL: TStringList;
XFTH: TCYThread;
Begin
XFSL:= TStringList.Create;
XFTH:= TCYThread.Create(True,AddrLan,CardNo,Data,Mode,XFSL);
//while WaitForSingleObject(BuTH.Handle,0) <> WAIT_OBJECT_0 do Application.ProcessMessages; //等待线程结束
XFTH.WaitFor;
XFTH.Destroy;
if XFSL.Count <= 0 then
begin
CanContinue:= False;
ErrorCode:= 40;
WriteException(FormatDateTime('yyy-mm-dd hh:mm:ss',now())+'消费事件线程执行失败!');
end
else
begin
if XFSL[3] <> '-1' then
begin
CanContinue:= False;
ErrorCode:= StrToIntDef(XFSL[3],36);
Exit;
end else
begin
CanContinue:= True;
CardBalance:= StrToFloatDef(XFSL[2],0);
SubsidyFund:= 0;
end;
end;
end;

鉴于可能并发刷卡,那我这消费事件是否也要写临界区呢,就是事件前面加进入临界区,事件结束的时候离开临界区,那如果是这样的话,外面的事件操作了临界区,线程里面也操作了临界区,这样会有影响么
funxu 2011-12-30
  • 打赏
  • 举报
回复
WaitFor是delphi封装好的函数,功能就是等待线程执行完毕,它里面调用了WaitForSingleObject,你可以看一下源码
function TThread.WaitFor: LongWord;
var
H: array[0..1] of THandle;
WaitResult: Cardinal;
Msg: TMsg;
begin
H[0] := FHandle;
if GetCurrentThreadID = MainThreadID then
begin
WaitResult := 0;
H[1] := SyncEvent;
repeat
{ This prevents a potential deadlock if the background thread
does a SendMessage to the foreground thread }
if WaitResult = WAIT_OBJECT_0 + 2 then
PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);
CheckThreadError(WaitResult <> WAIT_FAILED);
if WaitResult = WAIT_OBJECT_0 + 1 then
CheckSynchronize;
until WaitResult = WAIT_OBJECT_0;
end else WaitForSingleObject(H[0], INFINITE);
CheckThreadError(GetExitCodeThread(H[0], Result));
end;


WaitForSingleObject第二个参数是代表等待时间,你0用的不对,这样写的话可是会立即返回的
erhan 2011-12-29
  • 打赏
  • 举报
回复
同一事件近乎同时被触发,原事件是会被打断的。
我写了个小例子,你看下,应该有所领悟的。
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
for i:=1 to 1000 do
begin
Memo1.Lines.Add(IntToStr(i));
Application.ProcessMessages;
end;
end;
第一次按钮按后未执行完,再按一次,把Memo1的输出全选-复制-粘贴 到 记事本里看下,第一次按的被第二次打断了。
所以,该保护的一定要保护,临界区,只要是有可能并发的情况下就要用的。
funxu 2011-12-29
  • 打赏
  • 举报
回复
你可以把公用代码写成一个函数,在函数里写临界区也是一样的面还有线程返回值可以通过两种机制准确获得
1 如果线程运行完即返回可以使用waitforsingleobject来等待线程结束然后检查公用变量
2 如果线程要循环执行可以使用postthreadmessage来发送消息给主线程,告知返回值已经生成
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
而且我所有的变量都是在消费事件里定义的变量,没有公共或者全局变量
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
[Quote=引用 20 楼 erhan 的回复:]

我觉得吧,线程解决不了你的问题。你应该是在CRCLANX1Consume事件的代码里用了某个公共的变量啥的,导致程序跑飞了,我最早的那段代码看了吗,互斥一下。
[/Quote]
消费事件里以前就有加临界区,另外问一点,临界区是只能在线程里使用,还是在普通事件下也可以?
erhan 2011-12-29
  • 打赏
  • 举报
回复
我觉得吧,线程解决不了你的问题。你应该是在CRCLANX1Consume事件的代码里用了某个公共的变量啥的,导致程序跑飞了,我最早的那段代码看了吗,互斥一下。
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 erhan 的回复:]

按你上面的代码逻辑来说。
你的代码不都写了吗,返回值啥的加到了memo1里面,memo1的onChange事件里写代码,发现memo1的内容变化了,再处理不就行了。
[/Quote]
那个是例子~~,这样和你说吧,餐饮机有提供二次开发的接口,接口有提供比如消费,查询余额的事件,就是比如餐饮机在执行消费或者查询余额的时候会触发这个事件,然后执行里面的代码,返回相应的参数,比如余额,比如错误代码之类的,
procedure TFMainServer.CRCLANX1Consume(ASender: TObject; const AddrLan,
CardNo: WideString; Mode: Integer; const Data: WideString;
var CanContinue: WordBool; var CardBalance, SubsidyFund: Single;
var IsVerifyPassword: WordBool; var Password: WideString;
var ErrorCode: Integer);
begin
end;
AddLan是餐饮机的IP地址,CardNo是消费的卡号,CanContinue是判断是否能消费,ErrorCode是如果不能消费返回的错误代码,我们在这个事件下查询数据库信息对该卡号在该时间段消费进行判断,比如该卡所属的卡类能否在此餐饮机消费,比如能否在这个时间段消费之类的,如果判断都成立,就赋值CanContinue为true,然后赋值CardBalance(返回的余额),餐饮机就会响应,如果判断不成立,就赋值CanContinue为FALSE,然后赋值ErrorCode,餐饮机就会显示错误代码提示不能消费,原理是这样,但是并发数可能达到40-50这样的,就是40-50台餐饮机同时在这个时间点消费,同时触发该事件,查询数据库用的ADO我现在是在事件执行的时候单独创建,执行完了再释放掉,但是这种方法,当消费数量太大或者其它原因的时候,程序会自动退出导致餐饮机脱机,所以我想能否用线程解决这个问题,但是又必须在该事件下处理和餐饮机的交互~~~
erhan 2011-12-29
  • 打赏
  • 举报
回复
按你上面的代码逻辑来说。
你的代码不都写了吗,返回值啥的加到了memo1里面,memo1的onChange事件里写代码,发现memo1的内容变化了,再处理不就行了。
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
我现在关键就是要获取得到返回值,比如错误代码,余额啊什么的,才能与设备交互~~~
erhan 2011-12-29
  • 打赏
  • 举报
回复
memo1的onChange事件里写代码,肯定是传出来了,我用你的代码试了,memo1里面的串都加上了
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
在同一个事件里
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 erhan 的回复:]

你那上面showmessage都不对,应该是memo1,而且线程不一定运行完了,你就show了。
[/Quote]

我不是说showmessage memo1 的也是一样么,那个是后来测试的时候又改了,怎么等待线程结束后再获取呢
erhan 2011-12-29
  • 打赏
  • 举报
回复
你那上面showmessage都不对,应该是memo1,而且线程不一定运行完了,你就show了。
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 funxu 的回复:]

同意7L建议lz还是先学习下线程同步的知识然后再写代码
临界区的概念是:当多线程尝试执行某段相同代码时只会有一个线程可以进入临界区,其它线程会等待,所以这里的临界区需要保护的就是你多个线程需要执行的某段相同的代码,明白了?
[/Quote]

我知道写在哪,关键是有些代码无法写在线程里啊,比如我需要在消费事件里返回一些参数给设备,这些只能在该事件下才行,而且就像我上面贴出来的,我无法获取线程运行后得到的值
chhrsas 2011-12-29
  • 打赏
  • 举报
回复
工欲善其事,必先利其器
建议楼主还是先看看可以看万一的博客,上面有多线程入门,很适合你。
funxu 2011-12-29
  • 打赏
  • 举报
回复
同意7L建议lz还是先学习下线程同步的知识然后再写代码
临界区的概念是:当多线程尝试执行某段相同代码时只会有一个线程可以进入临界区,其它线程会等待,所以这里的临界区需要保护的就是你多个线程需要执行的某段相同的代码,明白了?
erhan 2011-12-29
  • 打赏
  • 举报
回复
其实我从没用过WaitForSingleObject这玩意,呵呵。

线程等结束,我一直用的是我写的那段代码。

没研究过那个,说不出差别来。
mengdiewufeng 2011-12-29
  • 打赏
  • 举报
回复
[Quote=引用 36 楼 erhan 的回复:]

如果只是为了等线程结束,还是建议你这样做
Delphi(Pascal) code

type
TCYThread = class(TThread)
private
mstr: string;
protected
procedure Execute;override;
procedure AddStr;
public
end;

var
……
[/Quote]

还真可以,两者之间有什么差别么
加载更多回复(20)

2,497

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 数据库相关
社区管理员
  • 数据库相关社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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