高效处理在主线程中显示后台线程处理进度

admin3个月前教程98

先看经典的处理方法:

TThread.CreateAnonymousThread(
    procedure
    var
      AHint: TZProgressNotify;
      ACount: Integer;
      ATime: Cardinal;
    const
      PassCount=1000000;
    begin
      ACount := 0;
      ATime := TThread.GetTickCount;
      while (not Application.Terminated) and (ACount < PassCount) do
      begin
        AHint.Progress := ACount*100 div PassCount ;
        AHint.HintText := ACount.ToString;
        Inc(ACount);        TThread.Synchronize(nil,
          procedure
          begin
            FProgress.Update(AHint);
          end
        );
        FProgress.Update(AHint);
      end;
      AHint.HintText := (TThread.GetTickCount - ATime).ToString + 'ms';
      FProgress.Update(AHint);
    end).Start;

我们需要更新进度时,将其切换到主线程,并更新进度显示。我们测试显示用了32735ms,也就是说100万次进度更新,用了约33秒。

接下来我们来看下优化后的代码:

TThread.CreateAnonymousThread(
    procedure
    var
      AHint: TZProgressNotify;
      ACount: Integer;
      ATime: Cardinal;
    const
      PassCount=1000000;
    begin
      ACount := 0;
      ATime := TThread.GetTickCount;
      while (not Application.Terminated) and (ACount < PassCount) do
      begin
        AHint.Progress := ACount*100 div PassCount ;
        AHint.HintText := ACount.ToString;
        Inc(ACount);        FProgress.Update(AHint);
      end;
      AHint.HintText := (TThread.GetTickCount - ATime).ToString + 'ms';
      FProgress.Update(AHint);
    end).Start;

对的,你没看错,我们将 FProgress.Update 直接在后台线程调用了。我们对其代码进行了逻辑隔离,实测 100 万次进度更新,用时 78 毫秒。

我们来看一下具体的实现:

type
  TZProgressNotify = record
    Progress: Integer;
    HintText: String;
  end;

TZMainThreadUpdator < T: record >= record
private
const
  FLAG_READING = Integer($80000000);
var
  FBuffers: array [0 .. 1] of T;
  FActiveIndex, FUpdateRefCount: Integer;
public
  procedure Update(const AValue: T);
  function GetData: T;overload;
  procedure GetData(var AValue: T);overload;
  property Data: T read GetData;
end;

{ TZMainThreadUpdator<T> }

procedure TZMainThreadUpdator<T>.GetData(var AValue: T);
// 值复制可能会引起冲突,我们需要避免在值复制时,外部更新,所以将 FActiveIndex 加入标志位
var
  ABufferIndex: Integer;
begin
  Assert(MainThreadId = TThread.Current.ThreadID,
    'GetData must invoke in main thread');
  //设置读取中标记位,设置后,Update不会更新 FActiveIndex 的值
  repeat
    ABufferIndex := FActiveIndex;
  until AtomicCmpExchange(FActiveIndex, ABufferIndex or FLAG_READING,
    ABufferIndex) = ABufferIndex;
  AValue := FBuffers[ABufferIndex];
  //允许后续的 Update 更新 FActiveIndex 的值以体现最新的进度
  AtomicExchange(FActiveIndex, ABufferIndex);
end;

function TZMainThreadUpdator<T>.GetData: T;
begin
  GetData(Result);
end;

procedure TZMainThreadUpdator<T>.Update(const AValue: T);
var
  ABufferIndex: Integer;
begin
  ABufferIndex := AtomicIncrement(FUpdateRefCount);
  try
    // 增加计数,如果有多个同时提交更新,只有第一个会保留,剩下的会丢弃
    if ABufferIndex = 1 then
    begin
      // 多个线程同时更新,只保留第一个更新线程的结果,避免使用锁
      ABufferIndex := (FActiveIndex + 1) and $1;
      FBuffers[ABufferIndex] := AValue;
      // 如果 FActiveIndex 不处于读状态,则更新,否则忽略更新,可以增加额外的标记来记录这个情况,然后在读取的时候清楚这一标记,本版本不做处理
      AtomicCmpExchange(FActiveIndex, ABufferIndex, FActiveIndex and $1);
    end;
  finally
    AtomicDecrement(FUpdateRefCount);
  end;
end;


标签: delphi

相关文章

一个共享的计时器类封装

QDAC 4.0 中已经包含了此单元,名称改为qdac.timer.share,使用 TQShareTimer 来做相关处理。这个是一个精度为秒的共享定时器实现,可以秒为单位创建多个共享的定时器,这些...

Delphi 函数的内部函数在匿名回调函数中无法使用的一种解决办法

这个问题很简单,将对应的函数改写赋值到一个匿名函数变量即可。function test;   function add(x,y:Integer):Intege...

Delphi 泛型中数据内容比较

第一:你需要引入 System.Generics.Defaults 单元,系统默认实现的比较方法,都在该单元定义。第二:你要明确下你的需求,是只比较相等就可以,还是要比较大小。IEqualityCom...

运行时动态修改 FMX 样式

运行时动态修改 FMX 样式

简单说几个点:1、FMX 框架下,样式是通过名称来标志的。2、GetStyleObject 默认是克隆原始的样式,所以直接修改对象自身的 FResourceLink 实例,不会影响其它样式,如果要修改...