要解决的问题: 有多个线程输出日志,日志内容需要在列表框中显示出来,不管日志输出的频率快慢,界面不能卡,不能闪烁。超过10万行日志时,自动删除最开始的1万行日志。
此问题涉及多线程编程,多线程输出时要更新界面的显示。
多线程的东西,当然不能忘了QWorker这样的神器,下面我们就来使用QWorker解决问题,哦,不对,是用YxdWorker来解决问题(为什么是YxdWorker?哈,这是宝宝的修改版本啦,封装成了自己喜欢的样子,其实和QWorker差不多,有兴趣可以到 SVN: https://github.com/yangyxd/YxdWorker下载。)
一、先在在搜索路径中添加对YxdWorker的引用,然后在窗体上放一个ListView, Name=ListView1,再放一个CheckBox, Name=CheckBox1。效果如下:
ListView1的OwnerData属性设为True,ViewStyle属性设为vsReport,添加一列“日志内容”。
二、 开始写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
unit Unit1; interface uses YxdWorker, SyncObjs, CommCtrl, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, StdCtrls; type TForm1 = class(TForm) CheckBox1: TCheckBox; ListView1: TListView; Button1: TButton; Button2: TButton; Button3: TButton; Button4: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure ListView1Data(Sender: TObject; Item: TListItem); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure Button1Click(Sender: TObject); procedure CheckBox1Click(Sender: TObject); private { Private declarations } FLogs: TStrings; // 存放日志内容 FAutoScroll: Boolean; // 是否自动滚动 FLogsIsDel: Boolean; // 是否已经删除日志 FLogRef: Integer; // 状态计数器 FLocker: TCriticalSection; FTestRef: Integer; public { Public declarations } procedure DoDataChange(); procedure DoWriteLog(Sender: TObject; const Log: string); procedure OnDataChange(AJob: PJob); procedure DoTest(AJob: PJob); procedure Log(const Text: string); end; var Form1: TForm1; implementation {$R *.dfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); begin Workers.Post(DoTest, nil); end; procedure TForm1.Button2Click(Sender: TObject); begin FLocker.Enter; FLogs.Clear; FLogsIsDel := True; FLocker.Leave; DoDataChange(); end; procedure TForm1.Button3Click(Sender: TObject); begin Log('写一行日志'); end; procedure TForm1.Button4Click(Sender: TObject); begin Workers.Clear(DoTest, nil); end; procedure TForm1.CheckBox1Click(Sender: TObject); begin FLocker.Enter; FAutoScroll := CheckBox1.Checked; FLocker.Leave; end; procedure TForm1.DoDataChange; begin // FLogRef 很关键,决定什么时候真正更新显示。 // 多线程写日志时,FLogRef > 1,那么也只更新一次。 if InterlockedIncrement(FLogRef) = 1 then // 延时50ms更新。此值设定的越大,列表更新的越慢。 // 不延时的话,界面刷新太快,占用资源会比较大 Workers.Post(OnDataChange, nil, True, 50) else InterlockedDecrement(FLogRef); end; procedure TForm1.DoTest(AJob: PJob); var I, M: Integer; begin M := 0; while (not AJob.IsTerminated) and (M < 10000) do begin Inc(M); I := InterlockedIncrement(FTestRef); Log(Format('日志内容。工作者:%d. (%d)', [AJob.Handle, I])); Sleep(100); end; end; procedure TForm1.DoWriteLog(Sender: TObject; const Log: string); var I: Integer; begin if Assigned(FLogs) and (Assigned(Self)) then begin FLocker.Enter; // 大于10万行时,删除前面的1万行 if FLogs.Count > 100000 then begin FLogsIsDel := True; for I := 10000 downto 0 do FLogs.Delete(I); end; // 添加当前日志内容 FLogs.Add('[' + FormatDateTime('hh:mm:ss.zzz', Now) + '] ' + Log); FLocker.Leave; // 产生一个变更通知 DoDataChange(); end; end; // 初始化 procedure TForm1.FormCreate(Sender: TObject); begin FLogsIsDel := False; FLogRef := 0; FTestRef := 0; FLogs := TStringList.Create(); FLocker := TCriticalSection.Create; FAutoScroll := CheckBox1.Checked; // 将最大工作者数量设置大一些。因为太少的话,工作者不够用,没有时间来显示日志了。 Workers.MaxWorkers := 512; end; procedure TForm1.FormDestroy(Sender: TObject); begin Workers.Clear(Self); FreeAndNil(FLogs); FreeAndNil(FLocker); end; procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem); begin FLocker.Enter; if Assigned(FLogs) and (Item.Index < FLogs.Count) then Item.Caption := FLogs[Item.Index]; FLocker.Leave; end; procedure TForm1.Log(const Text: string); begin if Assigned(Self) then DoWriteLog(Self, Text); end; procedure TForm1.OnDataChange(AJob: PJob); begin if Assigned(Self) and Assigned(FLogs) then begin if Assigned(ListView1) and (ListView1.HandleAllocated = True) then begin ListView_SetItemCountEx(ListView1.Handle, FLogs.Count, LVSICF_NOINVALIDATEALL or LVSICF_NOSCROLL); // 修改列表项的数量,并不改变滚动条位置 if FAutoScroll then SendMessage(ListView1.Handle, WM_VSCROLL, SB_BOTTOM, 0); if FLogsIsDel then begin FLogsIsDel := False; ListView1.Invalidate; end; end; InterlockedDecrement(FLogRef); end; end; end. |
核心有两点:
- 使用FLogRef计算器作为实际更新界面的依据。
- 使用ListView_SetItemCountEx宏来代替ListView.Count=xxxx更新列表框的行数。
- 使用临界锁来保持线程同步。
源码下载:
0 条评论
沙发空缺中,还不快抢~