DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
lazarus/fpc/Free Pascal
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: qiaoguoqiang
今日帖子: 0
在线用户: 1
导航: 论坛 -> 移动应用开发 斑竹:flyers,iamdream  
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/28 17:53:04
标题:
关于安卓线程报错的问题请教 浏览:725
加入我的收藏
楼主: 各位大佬,小弟又来打扰大家了,又碰到个问题,查了2天了没找到原因,请大家帮帮忙

问题点:如果使用线程就会报错,不使用线程就不会,报错类型如下图:


procedure TFOEE.SpeedButton1Click(Sender: TObject);
begin
   TThread.CreateAnonymousThread(FOEE.GetData).Start;
end;


而GetData就是执行几个小过程,小过程主要还是查询数据而已

procedure TFOEE.GetData;
begin
   if FOEE.TabControl1.ActiveTab = FOEE.TabItem1 then
   begin
      GetSmtTotailOEE;
      GetSmtLineOEE;
      GetSMTDownType;
   end;

   if FOEE.TabControl1.ActiveTab = FOEE.TabItem2 then
   begin
     GetMITotailOEE ;
     GetMILineOEE ;
   end;
end;
此帖子包含附件:
JPEG 图像
大小:338.6K
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/28 17:54:22
1楼: 这个警报只有第一次才会有,后面再点击按钮就不会再报出来
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 tuesdays (Tuesday) ▲▲▲▲△ -
普通会员
2023/9/28 18:07:04
2楼: 兄弟, 写代码, try要时刻用上啊. 
GetData
方法加个sleep, 让它稍慢几秒再工作.
----------------------------------------------
delphi界写python最强, python界写delphi最强. 写自己的代码, 让别人去运行.
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/28 18:12:58
3楼: 用了try也是一样的效果
procedure TFOEE.SpeedButton1Click(Sender: TObject);
begin
   try
   TThread.CreateAnonymousThread(FOEE.GetData).Start;
   except
      Exit;
   end;

end;
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/28 18:34:01
4楼: 另外还有一个现象

我在雷电安卓模拟器上不会报错,在真机上才会
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 k3man (嗯哼) ★☆☆☆☆ -
普通会员
2023/9/28 21:02:58
5楼: "Duplicates not allowed" 常在共享线程中,对某控件进行个性时,比如个性控件的选中状态时可能会发生。控件的选中状态对于多个线程是共享的,并且不支持在多个线程同时修改。

在 Delphi 中,UI 控件应该只在主线程中进行修改,这是为了确保线程安全性和正确的界面更新。因此,如果您在子线程中尝试修改 UI线程的界面状态,可能会导致错误。

要解决这个问题,您可以使用主线程来修改UI状态,或者在子线程中使用线程同步技术来正确地更新 UI 控件。以下是一个使用 TThread.Synchronize 方法在子线程中更新 TListView 选中状态的示例代码:

比如
TThread.Synchronize(nil,
    procedure
    begin
      ListView.Items[0].Selected := True;
      // 其他需要更新的选中状态操作
    end);
----------------------------------------------
-
作者:
男 k3man (嗯哼) ★☆☆☆☆ -
普通会员
2023/9/28 21:03:45
6楼: 把上面的“个性”换成“修改”。
----------------------------------------------
-
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/28 21:16:10
7楼: To:k3man

    非常感谢,您说的是对的,我把代码改成如下所述,问题解决了

   try
      TThread.CreateAnonymousThread(
      procedure
      begin
         TThread.Synchronize(nil,FOEE.GetData)
      end).Start;
   except
      Exit;
   end;
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 k3man (嗯哼) ★☆☆☆☆ -
普通会员
2023/9/28 21:38:15
8楼: 事实上,你这样操作可能并不理想,如果在GetSmtTotailOEE;
      GetSmtLineOEE;
      GetSMTDownType;
过程中,存在向后台查询数据的请求,可能在遇到延时情况下,界面上的一些动态效果并不会及时显示出来。因为繁忙操作被同步到主线程了。

简单点说就是把需要等待时间的操作放到子线程,然后在这个子线程中去同步UI线程。

希望论坛换改进下。手机体验太差,要死人的。
----------------------------------------------
-
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/28 22:35:21
9楼: 这个问题我还是有点没搞明白原理,为什么只有首次调用才会报错,后面为什么不会呢
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 emailx45 (emailx45) ▲▲▲▲△ -
普通会员
2023/9/29 1:42:46
10楼: I think that if you need to receive results after querying data, then you should use an IFuture instead of a conventional Thread only.

So, you could launch an anonymous thread or any other, preferably I think a "TASK" would be more suitable for parallel processing, and, from within this thread you would call IFUTURE tasks to process the query data.

In the end, you would have the expected results, however, in a more appropriate way and using the parallel processing power of your system

-- objects: TTask and IFuture

see my sample:
https://bbs.2ccc.com/topic.asp?topicid=680095


.
按此在新窗口浏览图片
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2023/9/29 2:07:37
11楼:    try
      TThread.CreateAnonymousThread(
      procedure
      begin
         TThread.Synchronize(nil,FOEE.GetData)
      end).Start;
   except
      Exit;
   end;

==========

这个写法,没用任何意义。

你把 GetData 放到线程里面执行的意义是什么?

照你上面的写法,相当于:

1. Button Click 是主线程,这个主线程的代码,创建了一个线程。
2. 这个线程的代码,调用了   TThread.Synchronize
3.   TThread.Synchronize 执行 GetData 实际上就是主线程执行 GetData;

那直接把 GetData 放到 Button Click 里面执行好了。何必转这么一圈?

----------

你的问题,本质是 5 楼说的问题,这个问题不只是在安卓里面才存在。只要是用 Delphi 写程序,不管是 WINDOWS 还是安卓,不管是 VCL 还是 FMX 框架,都有这个问题。

这个问题就是:不要用线程去修改界面 UI。

你的问题是 GetData 这个函数内部的代码,修改了界面 UI;

如果你直接把 GetData 放到线程里去执行,就会出问题。

但是,你的 GetData 可能有获取数据的代码,而获取数据可能会耗时,如果在主线程耗时很多,会导致界面冻结,在安卓系统里面,甚至会导致程序被系统杀死,因此你才需要把它放进线程。

但是,如果你把 GetData 放进线程,又会遇到线程调用 GetData 时,调用到 GetData 内部的和 UI 界面有关的代码,就会出顶楼描述的错误。甚至,还可能出其它错误。甚至,可能导致 APP 彻底失控或者崩溃。

那这个问题怎么处理?

很简单,修改 GetData 这个函数内部的代码,把凡是涉及到 UI 界面动作的代码,包到   TThread.Synchronize 里面去。

然后,GetData 这个函数本身,就可以用线程来调用了。
----------------------------------------------
-
作者:
男 k3man (嗯哼) ★☆☆☆☆ -
普通会员
2023/9/29 17:42:32
12楼: 文字通不过,截图。
此帖子包含附件:
JPEG 图像
大小:54.7K
----------------------------------------------
-
作者:
男 sxqwhxq (步惊云) ★☆☆☆☆ -
普通会员
2023/9/29 20:32:53
13楼: 各线程之间其实在不同的时空,不可穿越,不同线程可以访问主线程中的元素,包括组件、全局变量、对象等,前提是不可同时访问否则将导致冲突,为防止冲突需要使用同步技术。
----------------------------------------------
-
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/29 23:22:50
14楼: 我总算明白了,谢谢emailx45,pcplayer,k3man三位大佬的指点,同时也谢谢其他帮忙指点的朋友们

我先尝试修改GetData里面的代码,把界面UI部分区分,再结合emailx45的方式进行操作看看
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/29 23:29:07
15楼: 下面是我其中一个过程的代码,如果我没有理解错的话,这一部分代码应该要分成两个部分:

第一部分:数据查询,可以放在子线程里面
第二部分:涉及UI,在主线程执行 

    CDS1.Close;
      CDS1.CommandText:='select sum(Panels) as 生产板数,'+

      'sum(case when (DownTime > '''+ FDTime+''') and (PlanType=''计划外停机'' or PlanType is null) then DownTime else 0 end) as 计划外停机, '+

      'SUM(ATime) AS 计划开机时间,'+


      '(1-sum(case when DownTime > '''+ FDTime+''' then DownTime else 0 end)/SUM(ATime)) * 100 as 时间稼动率,'+

      'SUM(Points) AS 实际点数,'+

      'sum(IPC*(1-(case when DownTime > '''+ FDTime+''' then DownTime else 0 end)/ATime)) AS 理论点数,'+

      'SUM(Points)/sum(IPC*(1-(case when DownTime > 0 then DownTime else 0 end)/ATime)) *100 as 性能稼动率, '+

      '(1-(sum(case when (DownTime > '''+ FDTime+''') and (PlanType=''计划外停机'' or PlanType is null) then DownTime else 0 end))/'+
      '(SUM(ATime)-sum(case when PlanType=''计划内停机'' then DownTime else 0 end)) ) * '+
      '(SUM(Points)/sum(IPC*(1-(case when DownTime > 0 then DownTime else 0 end)/ATime))) * 100 as 设备OEE,'+
      'SUM(Points) / (DATEDIFF(minute, '''+SDate+''', getdate())/60.0) as 小时产能,'+
      'SUM(Points)/sum(Panels) as 单板点数,count(DISTINCT ProName) as 生产面数,'+
      'sum(case when (DownTime > '''+ FDTime+''') and (PlanType=''计划内停机'') then DownTime else 0 end) as 计划内停机,'+
      'SUM(Points) / (DATEDIFF(minute, '''+SDate+''', getdate())/60.0) * (DATEDIFF(minute, '''+SDate+''', '''+EDate+''')/60.0) as 产能预估  from '+


      '(select VITestData.ADate,VITestData.ALine,case when VITestData.AShift=0 then ''白班'' else ''晚班'' end as AShift,VITestData.ProName,'+
      'VITestData.ATime,SmtCycle.ACycle,VITestData.ATime - SmtCycle.ACycle as DownTime,ModuleList.Points * ModuleList.Panels as Points,'+
      'ALineList.IPC / 60 * ATime as IPC,'+
      'ModuleList.Panels,VITestData.CauType,DownType.PlanType from VITestData '+

      'left join (select ModuleID,Side,Panels,Points from ModuleList) as ModuleList '+
      'on ModuleList.ModuleID = VITestData.ModID and ModuleList.Side = VITestData.Side '+
      'left join SmtCycle on VITestData.ModID = SmtCycle.ModID and VITestData.Side = SmtCycle.Side and VITestData.ALine = SmtCycle.ALine '+
      'left join ALineList on VITestData.ALine = ALineList.ALine '+
      'left join DownType on DownType.DownType = VITestData.CauType '+
      'where (VITestData.ADate between '''+SDate+''' and '''+EDate+''') and (VITestData.ATime > 0)) as OEEData ';
      CDS1.Open;


上面部分是数据查询,可以在子线程运行,下面这一部分涉及UI,要在主线程运行,我先按自己的理解尝试修改一下

如果有错误的地方,希望大家继续指正,谢谢

      Label1.Text:=FormatFloat('0.0%',CDS1.FieldByName('设备OEE').AsFloat);
      Label6.Text:=FormatFloat('0.0%',CDS1.FieldByName('时间稼动率').AsFloat);
      Label3.Text:=FormatFloat('0.0%',CDS1.FieldByName('性能稼动率').AsFloat);
      Label4.Text:='设备OEE'+#13 + '<'+ FormatFloat('###,###,###,##0',CDS1.FieldByName('实际点数').AsFloat) +'>';

   if CDS1.FieldByName('设备OEE').AsFloat < GetStandValues('SMT设备OEE') then
   begin
     Circle1.Stroke.Color:=TAlphaColors.Tomato;
   end else
   begin
     Circle1.Stroke.Color:=TAlphaColors.Springgreen;
   end;
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 emailx45 (emailx45) ▲▲▲▲△ -
普通会员
2023/9/30 1:32:45
16楼: I think you could try the following:

1) as a DB query can be time-consuming, you could create a thread (TASK / IFUTURE) for it.
As what matters in this case is the result of the query, then a "task-IFUTURE" would be appropriate, as the rest of the initial procedure necessarily depends on the result of the query. So we're talking about "future thing", and that's where "IFUTURE" comes into play!

2) you can use as many TASK/IFUTURE as you need, however, you should always think that it may be necessary to cancel the task, so always provide a way to control the end of the task, if you wish. And also, always think that something can go wrong, so also think about handling an "exception", using the "TRY-EXCEPT" concept.

3) as a thread/task/ifuture can be running when the user closes the application, or form, so also think about controlling this, observing if there is any thread/task running, etc...

4) how task updates will be shown to the user, then also think about synchronizing thread/task with thread-main using the "synchronize/queue" procedures. As updates can be many, so also think about updating from time to time, that is, for example, every 10% update the screen, etc...

5) working with thread/task is not trivial, so it takes a lot of testing to find the most appropriate way, as there are several ways to do the same task. There is no magic recipe, however, there are more appropriate ways for each scenario.

6) always try to create small procedures, this way, you will have an easier way to find problems. If a process contains many lines of code, try dividing it into smaller portions. However, do not create too many, as this also makes it difficult to find errors. Maybe 10 lines of code would be ok, however, each case is different.

7) try to eliminate excessive texts within the code, for example, SQL expressions... this only hinders the search for errors. Try to create a unit just for the texts, for example, put your SQL expressions (text) in a unit specific to them, so, in your code to be executed, you can use the variables or constants that represent the text (SQL or no), example:

const
   // if you don't need to change the text
    MySQLText : string = 'xxxx xxxx xxxx';
var
    MySQLText : string = 'zzzz zzzz zzzz'; // if you need to use the variable for other text - not recommended

however, you can still change the value using parameters in Delphi, for example the "FORMAT()" function and "%s", "%d", etc... whether in constants or variables
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
作者:
男 kuei (kuei) ★☆☆☆☆ -
盒子活跃会员
2023/9/30 8:52:03
17楼: 采用自定义Event方式,将所有UI操作用onEvent导到Thread外处理

type
  TShowMsg = procedure(msg: string) of object;

  TEventBaseXE = class(TComponent)
  private
  FOnShowMSG: TShowMsgEvent;
  public  
  procedure ShowMsg(msg: string);
  published
    property OnShowMSG: TShowMsgEvent read FOnShowMSG write FOnShowMSG;
  end;  
  
procedure TEventBaseXE.ShowMsg(msg: string);
begin
  if Assigned(OnShowMsg) then
    FOnShowMsg(msg);
end;
----------------------------------------------
-
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/30 10:37:41
18楼: To:k3man

   我突然又有了一个疑问,如果UI属于主线程,那么我下面这个小例子理论上会报错,为何没有问题呢?

代码如下:

procedure TForm1.MyListStr;
var
  i,x:Integer;
begin
  for i := 1 to 10000 do
  begin
    x:=Random(10000);
    Memo1.Lines.Add(IntToStr(x));
    ListBox1.Items.Add(IntToStr(x));
  end;
  Button1.Text:=IntToStr(ListBox1.Items.Count)+'-'+IntToStr(Memo1.Lines.Count);
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
   TThread.CreateAnonymousThread(MyListStr).Start;
end;
此帖子包含附件:
PNG 图像
大小:41.6K
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2023/9/30 11:51:37
19楼: 18 楼:不是一定会报错。

但是,如果报错,也不一定是固定报一种错误。

总之用线程去修改 UI,可能会出各种奇怪的错误。甚至,程序崩溃。
----------------------------------------------
-
作者:
男 emailx45 (emailx45) ▲▲▲▲△ -
普通会员
2023/9/30 12:03:52
20楼: Memo1
ListBox1
Button1

3 visual elements  been accessed into thread context.... needs be Synchronized or Queued then main-thread can decide when update visual happens

NOTE: thumb-rule, in a thread not access any visual element! if need, then it should be synchronized/queued
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!RAD 11.3
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/30 13:33:18
21楼: 按照前面的理解,下面这种写法是否正确,还请大家指点一下,实际执行起来也不报错,看不出什么问题。

procedure TForm1.MyListStr;
begin
  SW:=TStopwatch.StartNew;
  SW.Start;
  MyList:=TStringList.Create;
  TTask.Run(
  procedure
  var
     i,x:Integer;
  begin
      for i := 1 to 10000 do
      begin
        x:=Random(10000);
        MyList.Add(IntToStr(x));
      end;

  TThread.Synchronize(nil,
  procedure
  begin
      Memo1.Lines:=MyList;
      Button1.Text:=IntToStr(Memo1.Lines.Count);
      Label1.Text:=SW.Elapsed.TotalMinutes.ToString;
  end);
  end);

  MyList.Free;
  SW.Stop;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Clear;
  Memo1.Text:='';
  MyListStr;
end;
此帖子包含附件:
PNG 图像
大小:29.1K
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2023/9/30 17:09:37
22楼: 21 楼的写法没有问题。

但是,考虑到代码的简单性,没必要的话,不要在一个函数里面包含2种东西。

比如,你这样写:

function GetListStr: TStringList;
begin
  Result := TStringList.Create;
  for .. do
  begin
   Result.Add('adfsdfasdfsd');
  end;
end;


procedure ShowList(List: TStringList);
var
  List: TStringList;
begin
  try
     for .... do Memo1.Lines.Add(....);
  finally
    List.Free;
  end;
end;


ButtonClick(..);
var
  List: TStringList;
begin
  TTask.Run(
  begin
    List := GetListStr;
  end;

  TThread.Synchronize(nil,
  procedure
  begin
    ShowList(List);
  end

);
end;


----------
又假如,你的那个原来的 MyListStr 里面,既有耗时的数据处理,也有界面显示,混在一起了,代码非常多,如果彻底重构很麻烦,不想那么麻烦,那么也很简单,就是把 MyListStr 里面,和界面有关的放进同步里面:但数据处理的,不要放线程里面。因为它只是数据处理,你在写代码的时候,无法预测是否需要把它放进线程。因此,应该这样写:

procedure TForm1.MyListStr;
begin
  SW:=TStopwatch.StartNew;
  SW.Start;
  MyList:=TStringList.Create;

  var
     i,x:Integer;
  begin
      for i := 1 to 10000 do
      begin
        x:=Random(10000);
        MyList.Add(IntToStr(x));
      end;

  TThread.Synchronize(nil,
  procedure
  begin
      Memo1.Lines:=MyList;
      Button1.Text:=IntToStr(Memo1.Lines.Count);
      Label1.Text:=SW.Elapsed.TotalMinutes.ToString;
  end);


  MyList.Free;
  SW.Stop;
end;

然后,调用这个函数的地方,加或者不加线程,都可以,视情况需要。也就是说,你写代码的时候,仅仅需要在涉及到 UI 的地方加上同步。最后这个函数在实际使用的时候,再根据情况决定要不要加线程。


procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Clear;
  Memo1.Text:='';
  MyListStr;
end;

改成:

procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Clear;
  Memo1.Text:='';
  TTask.Run(
  begin
    MyListStr;
  end
  );
end;
----------------------------------------------
-
作者:
男 changfenglee (葫芦老四) ★☆☆☆☆ -
普通会员
2023/9/30 19:41:03
23楼: 非常感谢,总算是有点眉目了,接下来我把原来的代码再重新写一下,看看效果怎么样。

再次感谢所有帮忙指导我的朋友们,现在DELPHI的净土不多了,真希望这个站能永久保持下去
----------------------------------------------
【个人签名】:玩了多年DELPHI,终于从菜鸟升级成老菜鸟
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v3.0.1 版权所有 页面执行169.9219毫秒 RSS