1 一个线程中可以去执行其它函数或者创建新的线程
2 线程虽然不能有返回值但是可以用加锁的全局变量实现返回值 百度synchronize
3 线程锁,这个问题有点宽泛,线程所有很多种,临界区,信号量,互斥等等,新手入门建议参考临界区,这个比较简单
InitializeCriticalSection(&cs);//初始化临界区 
EnterCriticalSection(&cs);//进入临界区
............//操作代码
LeaveCriticalSection(&cs);//离开临界区
DeleteCriticalSection(&cs);//删除临界区 

解决方案 »

  1.   

    话说这例子百度一搜一大堆,Lz查查就知道了
    http://wenku.baidu.com/view/0bb9df212f60ddccda38a0ee.html
      

  2.   

    建议楼主看下delphi5开发人员手册里面的线程讲的很详细
    ,另外1楼回答的很好,我也是刚解决了线程的问题
      

  3.   

    多线程在可能会出现并发访问一个全局变量或者调用外部一个函数(在不知其内部实际机制的情况下,可视为不支持多线程并发)时,需要使用互斥量(也叫信号灯)的机制在执行相应的代码时控制多线程进入排队执行的状态。因为是进入到串行状态,为了保证效率,不要把没必要互斥的代码放入互斥段。
    示例代码如下(大概是窗体主线程向一个邮件列表里加入待发邮件的信息,由邮件发送线程后台发送邮件):
    var
           CS:TRTLCriticalSection;
            mMailList: TList;
    procedure TMailThread.Execute();
    begin
            while not Terminated do
            begin
                    EnterCriticalSection(CS);      //进入临界区
                    i:=mMailList.Count;
                    LeaveCriticalSection(CS);      //离开临界区
                    while i > 0 do
                    begin
                                    //邮件处理代码省略
                                    EnterCriticalSection(CS);      //进入临界区
                                    mMailList.Delete(0);
                                    LeaveCriticalSection(CS);      //离开临界区
                                         dec(i);
                    end;
            end;
    end;procedure TMainForm.FormCreate(Sender: TObject);
    begin
            //初始化互斥变量
            InitializeCriticalSection(CS);
    end;procedure TMainForm.FormDestroy(Sender: TObject);
    begin
            DeleteCriticalSection(CS);
    end;//主线程向mMailList里加入新的item的时候也需要如上进入互斥。
    //主线程与邮件线程间为并发访问全局变量mMailList
      

  4.   

    感谢你的回答,
    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;
    这个为接口提供的消费事件,我如果仿造你给的文库的那个方法,把消费事件里的代码写在一个函数中,用CreateThread(nil,0,@MyThreadFun,nil,0,ID)这种来创建线程,是不是这样写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
      CreateThread(nil,0,@MyThreadFun,nil,0,ID);
      if ErrorCode <> -1 then
      begin
        CanContinue:= False;
        ErrorCode:= ErrorCode;
      end;
    end;把事件里的变量赋值到公共变量中,然后再在线程里引用,并且最终赋值结果,然后再判断?但是这样,如果我并发触发(多台餐饮机同时刷卡)了CRCLANX1Consume这个接口的事件,他会怎么运行呢
      

  5.   

    他的那个临界区进入和离开是写在线程的那个函数里,那我这样的,也是写在线程的那个函数(比如MyThreadFun)里么,还是CRCLANX1Consume里
      

  6.   

    因为接口问题,所以必须在该事件下处理才能与设备进行交互,以下是我写的简单的线程,我把公共变量的值赋值进去了,但是showmessage还是空值,这样的话,我如何在接口的消费事件利用线程呢unit Unit1;interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;type
      TForm1 = class(TForm)
        Edit1: TEdit;
        Button1: TButton;
        Memo1: TMemo;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
        Str2: string;
      end;
    type
      TTestThread = class(TThread)
      private
        Str: string;
        List: TMemo;
        SL: TStringList;
      procedure addToList;
      procedure Execute;override;
      end;var
      Form1: TForm1;implementation{$R *.dfm}procedure TForm1.Button1Click(Sender: TObject);
    var
      i: Integer;
      th1: TTestThread;
      Str1: string;
      TSL: TStringList;
      Mem: TMemo;
    begin
      TSL:= TStringList.Create;
      Mem:= TMemo.Create(Application);
      Mem.Parent:= Self;
      Mem.Visible:= False;
      th1:= TTestThread.Create(True);
      th1.List:= Memo1;
      th1.SL:= TSL;
      th1.Str:= Str2;
      th1.Resume;
      ShowMessage(str2);
    end;{ TTestThread }procedure TTestThread.addToList;
    begin
      List.Lines.add(Str);
      SL.Add(Str);
    end;procedure TTestThread.Execute;
    var
      i: Integer;
    begin
      inherited;
      for i:= 0 to 10 do
      begin
        Str:= IntToStr(i);
        Synchronize(addToList);
      end;
    end;
      

  7.   

    如果是showmessage SL 或者MEMO的值,也是一样
      

  8.   

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

  9.   

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

  10.   


    我不是说showmessage memo1 的也是一样么,那个是后来测试的时候又改了,怎么等待线程结束后再获取呢
      

  11.   

    memo1的onChange事件里写代码,肯定是传出来了,我用你的代码试了,memo1里面的串都加上了
      

  12.   

    按你上面的代码逻辑来说。
    你的代码不都写了吗,返回值啥的加到了memo1里面,memo1的onChange事件里写代码,发现memo1的内容变化了,再处理不就行了。
      

  13.   

    那个是例子~~,这样和你说吧,餐饮机有提供二次开发的接口,接口有提供比如消费,查询余额的事件,就是比如餐饮机在执行消费或者查询余额的时候会触发这个事件,然后执行里面的代码,返回相应的参数,比如余额,比如错误代码之类的,
    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我现在是在事件执行的时候单独创建,执行完了再释放掉,但是这种方法,当消费数量太大或者其它原因的时候,程序会自动退出导致餐饮机脱机,所以我想能否用线程解决这个问题,但是又必须在该事件下处理和餐饮机的交互~~~
      

  14.   

    我觉得吧,线程解决不了你的问题。你应该是在CRCLANX1Consume事件的代码里用了某个公共的变量啥的,导致程序跑飞了,我最早的那段代码看了吗,互斥一下。
      

  15.   

    你可以把公用代码写成一个函数,在函数里写临界区也是一样的面还有线程返回值可以通过两种机制准确获得
    1 如果线程运行完即返回可以使用waitforsingleobject来等待线程结束然后检查公用变量
    2 如果线程要循环执行可以使用postthreadmessage来发送消息给主线程,告知返回值已经生成
      

  16.   

    同一事件近乎同时被触发,原事件是会被打断的。
    我写了个小例子,你看下,应该有所领悟的。
    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的输出全选-复制-粘贴 到 记事本里看下,第一次按的被第二次打断了。
    所以,该保护的一定要保护,临界区,只要是有可能并发的情况下就要用的。
      

  17.   

    精读《windows核心编程》第8,9章
      

  18.   

    感谢你的回答,我也是这样写的,但是有个问题,我的代码卡在等待线程结束的那里了procedure TFMainServer.Button1Click(Sender: TObject);
    var
      YESL: TStringList;
      YETH: TRestaurantThread;
    Begin
      YESL:= TStringList.Create;
      YETH:= TRestaurantThread.Create(True);
      YETH.sAddrLan:= '192.168.23.100';
      YETH.sCardNo:= '0000200508';
      YETH.IsXF:= False;
      YETH.SL:= YESL;
      YETH.FreeOnTerminate:= True;
      YETH.Resume;
      while WaitForSingleObject(YETH.Handle,0) <> WAIT_OBJECT_0 do
        Application.ProcessMessages;
      if YESL.Count <= 0 then
      begin
        //CanContinue:= False;
        //ErrorCode:= 40;
        WriteException(FormatDateTime('yyy-mm-dd hh:mm:ss',now())+'查询余额事件线程执行失败!');
      end
      else
      begin
        if YESL[3] <> '-1' then
        begin
          //CanContinue:= False;
          //ErrorCode:= StrToIntDef(YESL[3],36);
          Exit;
        end else
        begin
          ShowMessage(YESL[2]);
          //SubsidyFund:= 0;
        end;
      end;
    end;卡在这句“while WaitForSingleObject(YETH.Handle,0) <> WAIT_OBJECT_0 do
        Application.ProcessMessages;”但是我跟踪执行的话,发现线程代码有执行完的说,
    以下为线程的代码:
      

  19.   


    //线程定义
    type
      TCYThread = class(TThread)
      private
        IsXF:Boolean;
        sAddrLan,sCardNo,sData: WideString;
        sCardBalance: Single;
        sErrorCode,sMode: Integer;
        procedure AddToList();
      protected
        procedure Execute;override;
      public
        SL: TStringList;
      end;
    //AddToList过程
    procedure TCYThread.AddToList;
    begin
      SL.Clear;
      SL.Add(sAddrLan);
      SL.Add(sCardNo);
      SL.Add(FloatToStr(sCardBalance));
      SL.Add(IntToStr(sErrorCode));
    end;
    //Execute代码
    procedure TCYThread.Execute;
    var
      li_CardBalance, li_ConsumeFund: Single;
      i, li_Copies, li_Count, n, paIndex,paIndex1,UseNum: Integer;
      S: WideString;
      EmplID,CYID,SiteID,CardSrtID,RepastID: Integer;
      IsPaused,IsAKQ, IsBCard: Boolean;
      CardPW,WareNo,SiteKind,CardNum,StrExp: String;
      Wareprice,AMoney,LeastMoney,LimitMoney,PMoney,Repastprice: Single;
      ValidityDT,XFDate: TDateTime;
      ADO_Select,ADO_Select1: TAdoQuery;
      BegTime,EndTime,XFTime: TTime;
      IsYC,IsXYBC : Boolean;
      ADOConnect: TADOConnection;
    Begin
      inherited;
      EnterCriticalSection(Critical1);
      sErrorCode:= -1;
      FreeOnTerminate:= True;
      if IsXF then
      begin
      try
        CardNum :='';
        XFDate := StrtoDate(Formatdatetime('yyyy-mm-dd',Date()));
        XFTime := StrtoTime(Formatdatetime('hh:mm:ss',Time()));
        CardNum := Formatfloat('0000000000',StrtoFloat(sCardNo));
        Paindex:=0;
        Try
          ADOConnect := TADOConnection.Create(nil);
          ADOConnect.LoginPrompt := False;
          ADOConnect.ConnectionString := 'Provider=SQLOLEDB.1;Password='+password+';Persist Security Info=True;User ID='+UserName+';Initial Catalog='+InitialData+';Data Source='+DataSource+'';
          ADOConnect.CommandTimeout := 1800;
          ADOConnect.Connected := True;
          ADO_Select := TADOQuery.Create(nil);
          ADO_Select.Connection := ADOConnect;
        except
          on e:exception do
          Begin
            StrExp := FormatDateTime('yyyy-mm-dd hh:mm:ss',now())+' ADOConnect :'+e.Message+'**ErrorCode:36';
            if ADOConnect <> nil then StrExp:= StrExp+':'+BoolToStr(ADOConnect.Connected);
            if ADO_Select <> nil then StrExp:= StrExp+','+ADO_Select.ConnectionString;
            FMainServer.WriteException(StrExp);
            sErrorCode :=36;
            if ADO_Select <> nil then FreeAndNil(ADO_Select);
            if ADOConnect <> nil then FreeAndNil(ADOConnect);
            Exit;
          End;
        End;
        //查询判断
      except
        //错误处理
      end;
      end;
      LeaveCriticalSection(Critical1);
      Synchronize(AddToList);  
      

  20.   

    你这代码有别的问题的,
    假设
          ADOConnect := TADOConnection.Create(nil);
         ADOConnect.LoginPrompt := False;
    其后一部分出现异常,导致except部分执行,会导致临界区未离开,进入死锁。
    临界区是需要用的时候再用,你贴出来的这部分代码似乎只有这一句可能需要互斥
    FMainServer.WriteException(StrExp);
      

  21.   

    那我加finally应该就可以解决你这个问题了,但是目前是,创建什么的都没问题,我跟踪下来都执行了,但是还是卡在那,感谢你的一直帮助
      

  22.   

    WaitForSingleObject的用法 参见
    http://www.cnblogs.com/andyhere/archive/2008/10/20/1314803.html
      

  23.   

    没错啊,我就是while WaitForSingleObject(YETH.Handle,0) <> WAIT_OBJECT_0 do
        Application.ProcessMessages;立即返回,然后判断的啊
      

  24.   

    如果只是为了等线程结束,还是建议你这样做type
      TCYThread = class(TThread)
      private
            mstr: string;
      protected
        procedure Execute;override;
        procedure AddStr;
      public
      end;var
      Form1: TForm1;
      mThread: TCYThread;implementation{$R *.dfm}procedure TForm1.Button1Click(Sender: TObject);
    begin
            mThread := TCYThread.Create(True);        mThread.FreeOnTerminate := False;
            mThread.Resume;        mThread.WaitFor;
            mThread.Destroy;        showmessage('aa');
    end;procedure TCYThread.AddStr();
    begin
            Form1.Memo1.Lines.Add(mstr);
    end;procedure TCYThread.Execute();
    var
            i: integer;
    begin
            for i:=1 to 1000 do
            begin
                    mstr := IntToStr(i);
                    Synchronize(AddStr);
            end;
    end;
      

  25.   

    其实我从没用过WaitForSingleObject这玩意,呵呵。线程等结束,我一直用的是我写的那段代码。没研究过那个,说不出差别来。
      

  26.   

    你好,我现在消费事件是这样写的,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;
    鉴于可能并发刷卡,那我这消费事件是否也要写临界区呢,就是事件前面加进入临界区,事件结束的时候离开临界区,那如果是这样的话,外面的事件操作了临界区,线程里面也操作了临界区,这样会有影响么
      

  27.   

    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用的不对,这样写的话可是会立即返回的