├── Tests └── GUI │ ├── Threading__test.res │ ├── Threading__test.dpr │ ├── Unit2.dfm │ ├── Unit2.pas │ └── Threading__test.dproj ├── Pkg.Threading.pas ├── .gitignore ├── Pkg.Threading.CancellationToken.pas └── Pkg.Threading.SyncObjs.pas /Tests/GUI/Threading__test.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PKGeorgiev/Delphi-Threading/HEAD/Tests/GUI/Threading__test.res -------------------------------------------------------------------------------- /Pkg.Threading.pas: -------------------------------------------------------------------------------- 1 | unit Pkg.Threading; 2 | 3 | interface 4 | uses sysUtils, classes; 5 | 6 | 7 | 8 | procedure RunInThread(AHandler: TProc); 9 | procedure RunInVcl(AHandler: TProc); 10 | 11 | implementation 12 | 13 | procedure RunInThread(AHandler: TProc); 14 | begin 15 | TThread.CreateAnonymousThread(AHandler).Start(); 16 | end; 17 | 18 | procedure RunInVcl(AHandler: TProc); 19 | begin 20 | TThread.Synchronize(nil, TThreadProcedure(AHandler)); 21 | end; 22 | 23 | end. 24 | -------------------------------------------------------------------------------- /Tests/GUI/Threading__test.dpr: -------------------------------------------------------------------------------- 1 | program Threading__test; 2 | 3 | uses 4 | Vcl.Forms, 5 | Unit2 in 'Unit2.pas' {Form2}, 6 | Pkg.Threading.CancellationToken in '..\..\Pkg.Threading.CancellationToken.pas', 7 | Pkg.Threading in '..\..\Pkg.Threading.pas', 8 | Pkg.Threading.SyncObjs in '..\..\Pkg.Threading.SyncObjs.pas'; 9 | 10 | {$R *.res} 11 | 12 | begin 13 | Application.Initialize; 14 | Application.MainFormOnTaskbar := True; 15 | Application.CreateForm(TForm2, Form2); 16 | Application.Run; 17 | end. 18 | -------------------------------------------------------------------------------- /Tests/GUI/Unit2.dfm: -------------------------------------------------------------------------------- 1 | object Form2: TForm2 2 | Left = 0 3 | Top = 0 4 | Caption = 'Form2' 5 | ClientHeight = 375 6 | ClientWidth = 852 7 | Color = clBtnFace 8 | Font.Charset = DEFAULT_CHARSET 9 | Font.Color = clWindowText 10 | Font.Height = -11 11 | Font.Name = 'Tahoma' 12 | Font.Style = [] 13 | OldCreateOrder = False 14 | OnCloseQuery = FormCloseQuery 15 | OnCreate = FormCreate 16 | PixelsPerInch = 96 17 | TextHeight = 13 18 | object Memo1: TMemo 19 | Left = 8 20 | Top = 8 21 | Width = 697 22 | Height = 345 23 | Lines.Strings = ( 24 | 'Memo1') 25 | TabOrder = 0 26 | end 27 | object Button1: TButton 28 | Left = 728 29 | Top = 8 30 | Width = 75 31 | Height = 25 32 | Caption = 'Button1' 33 | TabOrder = 1 34 | OnClick = Button1Click 35 | end 36 | object Button2: TButton 37 | Left = 728 38 | Top = 39 39 | Width = 75 40 | Height = 25 41 | Caption = 'Button2' 42 | TabOrder = 2 43 | OnClick = Button2Click 44 | end 45 | object Button3: TButton 46 | Left = 728 47 | Top = 88 48 | Width = 75 49 | Height = 25 50 | Caption = 'Button3' 51 | TabOrder = 3 52 | OnClick = Button3Click 53 | end 54 | object Button4: TButton 55 | Left = 728 56 | Top = 128 57 | Width = 75 58 | Height = 25 59 | Caption = 'Button4' 60 | TabOrder = 4 61 | OnClick = Button4Click 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | 25 | # Delphi compiler-generated binaries (safe to delete) 26 | *.exe 27 | *.dll 28 | *.bpl 29 | *.bpi 30 | *.dcp 31 | *.so 32 | *.apk 33 | *.drc 34 | *.map 35 | *.dres 36 | *.rsm 37 | *.tds 38 | *.dcu 39 | *.lib 40 | 41 | # Delphi autogenerated files (duplicated info) 42 | *.cfg 43 | *Resource.rc 44 | 45 | # Delphi local files (user-specific info) 46 | *.local 47 | *.identcache 48 | *.projdata 49 | *.tvsconfig 50 | *.dsk 51 | 52 | # Delphi history and backups 53 | __history/ 54 | *.~* 55 | -------------------------------------------------------------------------------- /Tests/GUI/Unit2.pas: -------------------------------------------------------------------------------- 1 | unit Unit2; 2 | 3 | interface 4 | 5 | uses 6 | Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 7 | Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Pkg.Threading.CancellationToken, 8 | Pkg.Threading, Pkg.Threading.SyncObjs, Vcl.StdCtrls, generics.collections, system.Diagnostics; 9 | 10 | type 11 | TForm2 = class(TForm) 12 | Memo1: TMemo; 13 | Button1: TButton; 14 | Button2: TButton; 15 | Button3: TButton; 16 | Button4: TButton; 17 | procedure FormCreate(Sender: TObject); 18 | procedure Button1Click(Sender: TObject); 19 | procedure Button2Click(Sender: TObject); 20 | procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); 21 | procedure Button3Click(Sender: TObject); 22 | procedure Button4Click(Sender: TObject); 23 | private 24 | { Private declarations } 25 | public 26 | { Public declarations } 27 | cts1, cts2, cts3: IPkgCancellationTokenSource; 28 | lcts1, lcts2, lcts3: IPkgCancellationTokenSource; 29 | ctsList: TList; 30 | end; 31 | 32 | var 33 | Form2: TForm2; 34 | 35 | implementation 36 | 37 | {$R *.dfm} 38 | 39 | procedure TForm2.Button1Click(Sender: TObject); 40 | begin 41 | RunInThread( 42 | procedure 43 | begin 44 | lcts3.Token.WaitHandle.WaitFor(INFINITE); 45 | lcts3.Token.ThrowIfCancellationRequested; 46 | RunInVcl( 47 | procedure 48 | begin 49 | memo1.Lines.Add('Finished1'); 50 | end 51 | ); 52 | end 53 | ); 54 | end; 55 | 56 | procedure TForm2.Button2Click(Sender: TObject); 57 | begin 58 | cts3.Cancel('Yooo'); 59 | // ApplicationCancellationTokenSource.Cancel(); 60 | end; 61 | 62 | 63 | procedure TForm2.Button3Click(Sender: TObject); 64 | var 65 | sw: TStopwatch; 66 | k: Integer; 67 | cts: IPkgCancellationTokenSource; 68 | begin 69 | ctsList.Clear; 70 | sw := TStopwatch.StartNew; 71 | sw.Start; 72 | for k := 1 to 1000 do 73 | begin 74 | cts := TPkgCancellationTokenSource.Create; 75 | ctsList.Add(cts); 76 | end; 77 | 78 | sw.Stop; 79 | memo1.Lines.Add(format('Create: %d', [sw.ElapsedMilliseconds])); 80 | end; 81 | 82 | procedure TForm2.Button4Click(Sender: TObject); 83 | var 84 | sw: TStopwatch; 85 | k: Integer; 86 | cts: IPkgCancellationTokenSource; 87 | begin 88 | 89 | sw := TStopwatch.StartNew; 90 | sw.Start; 91 | //ctsList.Clear; 92 | ApplicationCancellationTokenSource.Cancel(); 93 | 94 | sw.Stop; 95 | memo1.Lines.Add(format('Cancel: %d', [sw.ElapsedMilliseconds])); 96 | end; 97 | 98 | procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 99 | begin 100 | if ApplicationCancellationTokenSource.IsCancellationRequested then 101 | CanClose := true 102 | else 103 | begin 104 | CanClose := false; 105 | ApplicationCancellationTokenSource.Cancel(); 106 | end; 107 | end; 108 | 109 | procedure TForm2.FormCreate(Sender: TObject); 110 | begin 111 | 112 | cts1 := TPkgCancellationTokenSource.Create; 113 | cts2 := TPkgCancellationTokenSource.Create; 114 | cts3 := TPkgCancellationTokenSource.Create; 115 | // 116 | lcts1 := TPkgCancellationTokenSource.CreateLinkedTokenSource([cts1.Token]); 117 | lcts2 := TPkgCancellationTokenSource.CreateLinkedTokenSource([lcts1.Token, cts2.Token]); 118 | lcts3 := TPkgCancellationTokenSource.CreateLinkedTokenSource([lcts2.Token, cts3.Token]); 119 | 120 | ctsList := TList.Create; 121 | end; 122 | 123 | end. 124 | -------------------------------------------------------------------------------- /Pkg.Threading.CancellationToken.pas: -------------------------------------------------------------------------------- 1 | unit Pkg.Threading.CancellationToken; 2 | 3 | // A cancellation framework for Delphi 4 | // Inspired by .NET's cancellation framework with the following differences: 5 | // - Introduces AppTermCTS: a central place for cancellation 6 | // - All CTS' are linked in nature. There's no single CTS. 7 | // - All CTS' are linked to the AppTermCTS 8 | // - The properties of the token that caused cancellation are preserved 9 | // So ThrowIfCancelled will raise it's exception class and message 10 | // Can be used to propagate custom data 11 | 12 | interface 13 | uses Pkg.Threading.SyncObjs, sysUtils, generics.collections, syncObjs, classes; 14 | 15 | const 16 | COperationCancelled = 'Operation Cancelled!'; 17 | CApplicationTerminated = 'Operation cancelled due to program termination!'; 18 | 19 | type 20 | 21 | EOperationCancelled = class(Exception); 22 | EApplicationTerminated = class(EOperationCancelled); 23 | ETokenCanNotBeCancelled = class(EOperationCancelled); 24 | 25 | ECancellationClass = class of Exception; 26 | 27 | IPkgCancellationToken = interface; 28 | 29 | IPkgCancellationToken = interface ['{8E60410D-56B5-40BE-9B25-0FE125A8F79A}'] 30 | function getWaitHandle: IPkgWaitHandle; 31 | function getIsCancellationRequested: boolean; 32 | property WaitHandle: IPkgWaitHandle read getWaitHandle; 33 | property IsCancellationRequested: boolean read getIsCancellationRequested; 34 | procedure AddOnCancel(AHandler: TProc); 35 | procedure RemoveOnCancel(AHandler: TProc); 36 | procedure ThrowIfCancellationRequested; 37 | end; 38 | 39 | IPkgCancellationTokenSource = interface ['{E3633859-8D65-4F6B-B9E6-78E8BEAAE4C9}'] 40 | function getToken: IPkgCancellationToken; 41 | function getIsCancellationRequested: boolean; 42 | property Token: IPkgCancellationToken read getToken; 43 | property IsCancellationRequested: boolean read getIsCancellationRequested; 44 | function Cancel(AMessage: string = ''; AExceptionClass: ECancellationClass = nil; AThrowOnFirstException: boolean = false): boolean; 45 | end; 46 | 47 | TPkgLinkedCancellationTokenSource = class; 48 | TPkgCancellationToken = class(TInterfacedObject, IPkgCancellationToken) 49 | private 50 | // Weak ref to the parent CTS 51 | FCancellationTokenSource: TPkgLinkedCancellationTokenSource; 52 | // It's a pointer to be able to use Interlocked.CompareExchange 53 | // I.e. to skip TMonitor for synchronization 54 | FIsCancelled: Pointer; 55 | FCanBeCancelled: boolean; 56 | // TDictionary performs *much* better on item removal! 57 | FNotifyList: TDictionary, byte>; 58 | FEvent: IPkgEvent; 59 | FExceptionClass: ECancellationClass; 60 | FExceptionMessage: string; 61 | FAllowAttributeChange: boolean; 62 | FThrowOnFirstException: boolean; 63 | function getWaitHandle: IPkgWaitHandle; 64 | protected 65 | function InternalCancel(ACancellationToken: IPkgCancellationToken): boolean; 66 | function getIsCancellationRequested: boolean; 67 | procedure checkCancellable; 68 | function DoCancel(ACancellationToken: IPkgCancellationToken; AOnCancelHandler: TProc): boolean; 69 | public 70 | constructor Create(AIsCancelled: boolean); 71 | destructor Destroy; override; 72 | procedure AddOnCancel(AHandler: TProc); 73 | procedure RemoveOnCancel(AHandler: TProc); 74 | procedure ThrowIfCancellationRequested; virtual; 75 | property WaitHandle: IPkgWaitHandle read getWaitHandle; 76 | property IsCancellationRequested: boolean read getIsCancellationRequested; 77 | class function None: IPkgCancellationToken; 78 | end; 79 | 80 | TPkgLinkedCancellationTokenSource = class(TInterfacedObject, IPkgCancellationTokenSource) 81 | private 82 | FCancellationToken: IPkgCancellationToken; 83 | FLinkedCancellationTokens: TList; 84 | FNotifyProc: TProc; 85 | function getToken: IPkgCancellationToken; 86 | function getIsCancellationRequested: boolean; 87 | protected 88 | public 89 | constructor Create(ATokens: array of IPkgCancellationToken); 90 | destructor Destroy; override; 91 | property Token: IPkgCancellationToken read getToken; 92 | function Cancel(AMessage: string = ''; AExceptionClass: ECancellationClass = nil; AThrowOnFirstException: boolean = false): boolean; 93 | property IsCancellationRequested: boolean read getIsCancellationRequested; 94 | end; 95 | 96 | // By default all CTS are linked to the AppTermCTS 97 | // So TPkgCancellationTokenSource is just an illusion 98 | TPkgCancellationTokenSource = class(TPkgLinkedCancellationTokenSource, IPkgCancellationTokenSource) 99 | private 100 | protected 101 | public 102 | constructor Create; 103 | destructor Destroy; override; 104 | class function CreateLinkedTokenSource(ATokens: array of IPkgCancellationToken): IPkgCancellationTokenSource; 105 | end; 106 | 107 | // Returns AppTermCTS 108 | function ApplicationCancellationTokenSource: IPkgCancellationTokenSource; 109 | 110 | implementation 111 | 112 | type 113 | // The AppTermCTS. All CTS are linked to the AppTermCTS 114 | // So it is possible to cancel all CTS from one place 115 | // This class replaces only default ExceptionMessage and ExceptionClass 116 | TPkgApplicationCancellationTokenSource = class(TPkgCancellationTokenSource, IPkgCancellationTokenSource) 117 | public 118 | constructor Create; 119 | end; 120 | 121 | var 122 | AppCancellationTokenSource, TmpAppCancellationTokenSource: IPkgCancellationTokenSource; 123 | 124 | function ApplicationCancellationTokenSource: IPkgCancellationTokenSource; 125 | begin 126 | result := AppCancellationTokenSource; 127 | end; 128 | 129 | { TPkgCancellationToken } 130 | 131 | function TPkgCancellationToken.InternalCancel(ACancellationToken: IPkgCancellationToken): boolean; 132 | var 133 | LHandler: TProc; 134 | begin 135 | result := true; 136 | FEvent.setEvent; 137 | TMonitor.Enter(FNotifyList); 138 | try 139 | for LHandler in FNotifyList.Keys do 140 | begin 141 | LHandler(ACancellationToken); 142 | end; 143 | finally 144 | TMonitor.Exit(FNotifyList); 145 | end; 146 | end; 147 | 148 | procedure TPkgCancellationToken.checkCancellable; 149 | begin 150 | if not FCanBeCancelled then 151 | raise ETokenCanNotBeCancelled.Create('The Cancellation Token does not support cancellation!'); 152 | end; 153 | 154 | constructor TPkgCancellationToken.Create(AIsCancelled: boolean); 155 | begin 156 | inherited Create; 157 | FCanBeCancelled := true; 158 | FExceptionClass := EOperationCancelled; 159 | FExceptionMessage := COperationCancelled; 160 | FAllowAttributeChange := true; 161 | 162 | if AIsCancelled then 163 | FIsCancelled := Pointer(1) 164 | else 165 | FIsCancelled := nil; 166 | 167 | FNotifyList := TDictionary, byte>.Create; //TInterfaceList.Create;// TList>.Create; 168 | FEvent := TPkgEvent.Create(true, false); 169 | end; 170 | 171 | destructor TPkgCancellationToken.Destroy; 172 | begin 173 | FreeAndNil(FNotifyList); 174 | inherited; 175 | end; 176 | 177 | function TPkgCancellationToken.DoCancel(ACancellationToken: IPkgCancellationToken; AOnCancelHandler: TProc): boolean; 178 | begin 179 | checkCancellable; 180 | if TInterlocked.CompareExchange(Pointer(FIsCancelled), Pointer(1), Pointer(0)) = Pointer(0) then 181 | begin 182 | result := true; 183 | AOnCancelHandler(self); 184 | InternalCancel(ACancellationToken); 185 | end 186 | else 187 | result := false; 188 | end; 189 | 190 | function TPkgCancellationToken.getIsCancellationRequested: boolean; 191 | begin 192 | result := FIsCancelled <> nil; 193 | end; 194 | 195 | function TPkgCancellationToken.getWaitHandle: IPkgWaitHandle; 196 | begin 197 | result := FEvent.ToWaitHandle; 198 | end; 199 | 200 | class function TPkgCancellationToken.None: IPkgCancellationToken; 201 | begin 202 | result := TPkgCancellationToken.Create(false); 203 | TPkgCancellationToken(result).FCanBeCancelled := false; 204 | end; 205 | 206 | procedure TPkgCancellationToken.RemoveOnCancel( 207 | AHandler: TProc); 208 | var 209 | i: IInterface absolute AHandler; 210 | begin 211 | TMonitor.Enter(FNotifyList); 212 | try 213 | FNotifyList.Remove(AHandler); 214 | finally 215 | TMonitor.Exit(FNotifyList); 216 | end; 217 | end; 218 | 219 | procedure TPkgCancellationToken.AddOnCancel(AHandler: TProc); 220 | var 221 | i: IInterface absolute AHandler; 222 | begin 223 | if FIsCancelled <> nil then 224 | AHandler(self as IPkgCancellationToken) 225 | else 226 | begin 227 | TMonitor.Enter(FNotifyList); 228 | try 229 | FNotifyList.Add(AHandler, 0); 230 | finally 231 | TMonitor.Exit(FNotifyList); 232 | end; 233 | end; 234 | end; 235 | 236 | procedure TPkgCancellationToken.ThrowIfCancellationRequested; 237 | begin 238 | if FIsCancelled <> nil then 239 | raise FExceptionClass.Create(FExceptionMessage); 240 | end; 241 | 242 | { TPkgLinkedCancellationTokenSource } 243 | 244 | function TPkgLinkedCancellationTokenSource.Cancel(AMessage: string; AExceptionClass: ECancellationClass; AThrowOnFirstException: boolean): boolean; 245 | var 246 | LToken: TPkgCancellationToken; 247 | begin 248 | LToken := TPkgCancellationToken(FCancellationToken); 249 | result := LToken.DoCancel(FCancellationToken, 250 | procedure(AToken: TPkgCancellationToken) 251 | begin 252 | // Setup Initiator token's properties 253 | if AMessage <> '' then 254 | AToken.FExceptionMessage := AMessage; 255 | 256 | if AExceptionClass <> nil then 257 | AToken.FExceptionClass := AExceptionClass; 258 | 259 | AToken.FThrowOnFirstException := AThrowOnFirstException; 260 | end 261 | ); 262 | end; 263 | 264 | constructor TPkgLinkedCancellationTokenSource.Create( 265 | ATokens: array of IPkgCancellationToken); 266 | var 267 | LToken: IPkgCancellationToken; 268 | begin 269 | inherited Create; 270 | FCancellationToken := TPkgCancellationToken.Create(false); 271 | TPkgCancellationToken(FCancellationToken).FCancellationTokenSource := self; 272 | FLinkedCancellationTokens := TList.Create; 273 | // Add the Application Cancellation Token Source 274 | // This way if the Application was terminated all cancellation listeners will cancel 275 | if not (self is TPkgApplicationCancellationTokenSource) then 276 | FLinkedCancellationTokens.Add(ApplicationCancellationTokenSource.Token); 277 | FLinkedCancellationTokens.AddRange(ATokens); 278 | 279 | FNotifyProc := 280 | procedure(AToken: IPkgCancellationToken) 281 | begin 282 | if TPkgCancellationToken(FCancellationToken).DoCancel(AToken, 283 | procedure(BToken: TPkgCancellationToken) 284 | begin 285 | // Bubble up source token's attributes 286 | // i.e. ThowIfCancelled will raise FExceptionClass with FExceptionMessage 287 | BToken.FExceptionClass := TPkgCancellationToken(AToken).FExceptionClass; 288 | BToken.FExceptionMessage := TPkgCancellationToken(AToken).FExceptionMessage; 289 | BToken.FThrowOnFirstException := TPkgCancellationToken(AToken).FThrowOnFirstException; 290 | end 291 | ) then 292 | begin 293 | // This will be executed only once! NOP for now 294 | end 295 | end; 296 | 297 | for LToken in FLinkedCancellationTokens do 298 | begin 299 | LToken.AddOnCancel(FNotifyProc); 300 | end; 301 | end; 302 | 303 | destructor TPkgLinkedCancellationTokenSource.Destroy; 304 | var 305 | LToken: IPkgCancellationToken; 306 | begin 307 | for LToken in FLinkedCancellationTokens do 308 | begin 309 | LToken.RemoveOnCancel(FNotifyProc); 310 | end; 311 | FCancellationToken := nil; 312 | FreeAndNil(FLinkedCancellationTokens); 313 | inherited; 314 | end; 315 | 316 | function TPkgLinkedCancellationTokenSource.getIsCancellationRequested: boolean; 317 | begin 318 | result := FCancellationToken.IsCancellationRequested; 319 | end; 320 | 321 | function TPkgLinkedCancellationTokenSource.getToken: IPkgCancellationToken; 322 | begin 323 | result := FCancellationToken; 324 | end; 325 | 326 | { TPkgCancellationTokenSource } 327 | 328 | constructor TPkgCancellationTokenSource.Create(); 329 | begin 330 | inherited Create([]); 331 | end; 332 | 333 | class function TPkgCancellationTokenSource.CreateLinkedTokenSource( 334 | ATokens: array of IPkgCancellationToken): IPkgCancellationTokenSource; 335 | begin 336 | result := TPkgLinkedCancellationTokenSource.Create(ATokens) as IPkgCancellationTokenSource; 337 | end; 338 | 339 | destructor TPkgCancellationTokenSource.Destroy; 340 | begin 341 | inherited; 342 | end; 343 | 344 | { TPkgApplicationCancellationTokenSource } 345 | 346 | constructor TPkgApplicationCancellationTokenSource.Create; 347 | begin 348 | inherited Create; 349 | TPkgCancellationToken(FCancellationToken).FExceptionClass := EApplicationTerminated; 350 | TPkgCancellationToken(FCancellationToken).FExceptionMessage := CApplicationTerminated; 351 | end; 352 | 353 | initialization 354 | 355 | // Create the only one AppTermCTS 356 | TmpAppCancellationTokenSource := TPkgApplicationCancellationTokenSource.Create(); 357 | if TInterlocked.CompareExchange(Pointer(AppCancellationTokenSource), Pointer(TmpAppCancellationTokenSource), Pointer(nil)) <> nil then 358 | TmpAppCancellationTokenSource := nil; 359 | 360 | finalization 361 | // Just for clarity 362 | TInterlocked.CompareExchange(Pointer(AppCancellationTokenSource), Pointer(nil), Pointer(AppCancellationTokenSource)); 363 | 364 | end. 365 | -------------------------------------------------------------------------------- /Pkg.Threading.SyncObjs.pas: -------------------------------------------------------------------------------- 1 | unit Pkg.Threading.SyncObjs; 2 | 3 | interface 4 | uses syncObjs, {$IFDEF MSWINDOWS}Windows,{$ENDIF MSWINDOWS} sysUtils, 5 | generics.collections, system.threading; 6 | 7 | type 8 | 9 | IPkgWaitHandle = interface ['{A11FED0F-204A-4FE6-BED1-5E2D902A7A4F}'] 10 | function WaitFor(Timeout: LongWord): TWaitResult; 11 | function ToWaitHandle: IPkgWaitHandle; 12 | end; 13 | 14 | IPkgEvent = interface(IPkgWaitHandle) ['{F1CD8006-91EB-4ED7-BB15-423D9C4F3792}'] 15 | procedure setEvent; 16 | procedure resetEvent; 17 | end; 18 | 19 | IPkgSemaphore = interface(IPkgWaitHandle) ['{0960C375-C6AA-470D-ABA9-EEBE28D0E942}'] 20 | procedure Acquire; 21 | procedure Release; 22 | end; 23 | 24 | // For accessing TTask's protected members 25 | TPkgTaskHack = class(System.Threading.TTask) 26 | end; 27 | 28 | IPkgTaskWaiter = interface(IPkgWaitHandle) ['{6D28E618-8FEF-441B-92EB-4F1F92130BF2}'] 29 | end; 30 | 31 | TPkgWaitHandle = class(TInterfacedObject, IPkgWaitHandle) 32 | private 33 | FPadlock: TObject; 34 | FNotifyList: TList>; 35 | FWaiterCount: integer; 36 | FIsManualReset: boolean; 37 | protected 38 | procedure incWaiters; 39 | procedure decWaiters; 40 | procedure doNotify(ANotifyCount: integer); 41 | procedure atomic(AHandler: TProc); 42 | procedure lock; 43 | procedure unlock; 44 | procedure addWaiter(AWaiter: TProc); 45 | procedure removeWaiter(AWaiter: TProc); 46 | 47 | public 48 | constructor Create(AManualResetEvent: boolean); 49 | destructor Destroy; override; 50 | 51 | function WaitFor(Timeout: LongWord): TWaitResult; virtual; 52 | 53 | class function WaitAny1(AItems: array of IPkgWaitHandle; ATimeout: LongWord): integer; 54 | class function WaitAny(AItems: array of const; ATimeout: LongWord): integer; overload; 55 | class function WaitAny(AItems: TArray; ATimeout: LongWord): integer; overload; 56 | class function WaitList(AItems: array of const): TArray; 57 | 58 | function ToWaitHandle: IPkgWaitHandle; 59 | end; 60 | 61 | TPkgEvent = class(TPkgWaitHandle, IPkgWaitHandle, IPkgEvent) 62 | private 63 | FEvent: TEvent; 64 | protected 65 | public 66 | constructor Create(AManualResetEvent, AInitialState: boolean); 67 | destructor Destroy; override; 68 | 69 | procedure setEvent; 70 | procedure resetEvent; 71 | function WaitFor(ATimeout: LongWord): TWaitResult; override; 72 | end; 73 | 74 | TPkgSemaphore = class(TPkgWaitHandle, IPkgWaitHandle, IPkgSemaphore) 75 | private 76 | FSemaphore: TSemaphore; 77 | FInitialCount, 78 | FCurrentCount, 79 | FMaxCount: integer; 80 | protected 81 | public 82 | constructor Create(AInitialCount, AMaximumCount: Integer); 83 | destructor Destroy; override; 84 | 85 | procedure Acquire; 86 | procedure Release; 87 | function WaitFor(ATimeout: LongWord): TWaitResult; override; 88 | end; 89 | 90 | TPkgTaskWaiter = class(TPkgWaitHandle, IPkgWaitHandle, IPkgTaskWaiter) 91 | private 92 | FTask: ITask; 93 | FEvent: TLightweightEvent; 94 | FTaskCompletionHandler: TProc; 95 | protected 96 | public 97 | constructor Create(ATask: ITask); 98 | destructor Destroy; override; 99 | 100 | function WaitFor(ATimeout: LongWord): TWaitResult; override; 101 | end; 102 | 103 | TPkgAbstractExternalWaitHandle = class abstract(TPkgWaitHandle, IPkgWaitHandle) 104 | 105 | end; 106 | 107 | TPkgExternalEvent = class(TPkgEvent, IPkgEvent) 108 | 109 | end; 110 | 111 | 112 | 113 | 114 | 115 | implementation 116 | uses strUtils; 117 | 118 | 119 | 120 | { TPkgWaitable } 121 | 122 | constructor TPkgWaitHandle.Create(AManualResetEvent: boolean); 123 | begin 124 | inherited Create; 125 | FPadlock := TObject.Create; 126 | FNotifyList := TList>.Create; 127 | FIsManualReset := AManualResetEvent; 128 | end; 129 | 130 | 131 | procedure TPkgWaitHandle.decWaiters; 132 | begin 133 | TMonitor.Enter(FPadlock); 134 | try 135 | dec(FWaiterCount); 136 | finally 137 | TMonitor.Exit(FPadlock); 138 | end; 139 | end; 140 | 141 | destructor TPkgWaitHandle.Destroy; 142 | begin 143 | FreeAndNil(FNotifyList); 144 | FreeAndNil(FPadlock); 145 | inherited; 146 | end; 147 | 148 | procedure TPkgWaitHandle.incWaiters; 149 | begin 150 | TMonitor.Enter(FPadlock); 151 | try 152 | inc(FWaiterCount); 153 | finally 154 | TMonitor.Exit(FPadlock); 155 | end; 156 | end; 157 | 158 | class function TPkgWaitHandle.WaitAny(AItems: array of const; 159 | ATimeout: LongWord): integer; 160 | begin 161 | result := WaitAny(TPkgWaitHandle.WaitList(AItems), ATimeout); 162 | end; 163 | 164 | class function TPkgWaitHandle.WaitAny(AItems: TArray; 165 | ATimeout: LongWord): integer; 166 | var 167 | LItems: array of IPkgWaitHandle; 168 | k: Integer; 169 | begin 170 | setLength(LItems, length(AItems)); 171 | for k := low(AItems) to high(AItems) do 172 | LItems[k] := AItems[k]; 173 | 174 | result := waitAny1(LItems, ATimeout); 175 | setLength(LItems, 0); 176 | 177 | 178 | 179 | end; 180 | 181 | function TPkgWaitHandle.WaitFor(Timeout: LongWord): TWaitResult; 182 | begin 183 | raise Exception.Create('Not Implemented!'); 184 | end; 185 | 186 | class function TPkgWaitHandle.WaitList( 187 | AItems: array of const): TArray; 188 | var 189 | k: Integer; 190 | i: IInterface; 191 | o: TObject; 192 | LClassName: string; 193 | begin 194 | setLength(result, length(AItems)); 195 | for k := low(AItems) to high(AItems) do 196 | begin 197 | if TVarRec(AItems[k]).VType <> vtInterface then 198 | raise Exception.CreateFmt('Element %d is not an interface!', [k]); 199 | 200 | i := IInterface(AItems[k].VInterface); 201 | o := TObject(i); 202 | LClassName := LowerCase(o.QualifiedClassName); 203 | 204 | if not o.InheritsFrom(TPkgWaitHandle) then 205 | if o.InheritsFrom(System.Threading.TTask) then 206 | i := TPkgTaskWaiter.Create(ITask(i)) 207 | else 208 | raise Exception.CreateFmt('Parameter %d must be a Waitable or an ITask!', [k]); 209 | 210 | result[k] := (i as IPkgWaitHandle); 211 | end; 212 | end; 213 | 214 | class function TPkgWaitHandle.WaitAny1(AItems: array of IPkgWaitHandle; 215 | ATimeout: LongWord): integer; 216 | var 217 | LWaitable, LSignaledWaitable: IPkgWaitHandle; 218 | k: integer; 219 | LEvent: TLightweightEvent; 220 | LSignaledProc: TProc; 221 | LWaitRes: TWaitResult; 222 | begin 223 | LSignaledWaitable := nil; 224 | result := -1; 225 | // First check if we have already signalled events 226 | for k := low(AItems) to high(AItems) do 227 | begin 228 | LWaitable := AItems[k]; 229 | if LWaitable.WaitFor(0) = wrSignaled then 230 | begin 231 | exit(k); 232 | end; 233 | end; 234 | 235 | if (result = -1) AND (length(AItems) > 0) then 236 | begin 237 | LEvent := TLightweightEvent.Create(false); 238 | try 239 | // Local OnSignaled handler 240 | LSignaledProc := 241 | procedure(AWaitable: IPkgWaitHandle) 242 | begin 243 | if TInterlocked.CompareExchange(pointer(LSignaledWaitable), pointer(AWaitable), pointer(nil)) = nil then 244 | begin 245 | LEvent.SetEvent; 246 | end; 247 | 248 | TPkgWaitHandle(AWaitable).removeWaiter(LSignaledProc); 249 | 250 | end; 251 | 252 | for k := low(AItems) to high(AItems) do 253 | begin 254 | LWaitable := AItems[k]; 255 | TPkgWaitHandle(LWaitable).lock; 256 | try 257 | // Again, check for already signaled events 258 | if LWaitable.WaitFor(0) = wrSignaled then 259 | begin 260 | // Execute Local Handler 261 | LSignaledProc(LWaitable); 262 | break; 263 | end 264 | else 265 | // Add Local Handler to Event's Notify list 266 | TPkgWaitHandle(LWaitable).FNotifyList.Add(LSignaledProc); 267 | finally 268 | TPkgWaitHandle(LWaitable).unlock; 269 | end; 270 | 271 | end; 272 | 273 | LWaitRes := LEvent.WaitFor(ATimeout); 274 | 275 | // Update Event Index and remove Local Handlers 276 | for k := low(AItems) to high(AItems) do 277 | begin 278 | LWaitable := AItems[k]; 279 | TPkgWaitHandle(LWaitable).lock; 280 | try 281 | if (LSignaledWaitable = LWaitable) AND (LWaitRes <> wrTimeout) then 282 | result := k; 283 | 284 | TPkgWaitHandle(LWaitable).FNotifyList.Remove(LSignaledProc); 285 | 286 | finally 287 | TPkgWaitHandle(LWaitable).unlock; 288 | end; 289 | end; 290 | 291 | finally 292 | FreeAndNil(LEvent); 293 | end; 294 | end; 295 | 296 | 297 | end; 298 | 299 | procedure TPkgWaitHandle.doNotify(ANotifyCount: integer); 300 | var 301 | LNotifyProc: TProc; 302 | k, cnt: integer; 303 | begin 304 | cnt := 0; 305 | if FWaiterCount = 0 then 306 | begin 307 | for k := FNotifyList.Count - 1 downto 0 do 308 | begin 309 | LNotifyProc := FNotifyList[k]; 310 | LNotifyProc(self as IPkgWaitHandle); 311 | inc(cnt); 312 | if (ANotifyCount > 0) AND (cnt >= ANotifyCount) then exit; 313 | end; 314 | end; 315 | end; 316 | 317 | procedure TPkgWaitHandle.addWaiter(AWaiter: TProc); 318 | begin 319 | TMonitor.Enter(FPadlock); 320 | try 321 | FNotifyList.Add(AWaiter); 322 | finally 323 | TMonitor.Exit(FPadlock); 324 | end; 325 | end; 326 | 327 | procedure TPkgWaitHandle.atomic(AHandler: TProc); 328 | begin 329 | TMonitor.Enter(FPadlock); 330 | try 331 | AHandler; 332 | finally 333 | TMonitor.Exit(FPadlock); 334 | end; 335 | end; 336 | 337 | procedure TPkgWaitHandle.lock; 338 | begin 339 | TMonitor.Enter(FPadlock); 340 | end; 341 | 342 | procedure TPkgWaitHandle.removeWaiter(AWaiter: TProc); 343 | begin 344 | TMonitor.Enter(FPadlock); 345 | try 346 | FNotifyList.Remove(AWaiter); 347 | finally 348 | TMonitor.Exit(FPadlock); 349 | end; 350 | end; 351 | 352 | function TPkgWaitHandle.ToWaitHandle: IPkgWaitHandle; 353 | begin 354 | result := self as IPkgWaitHandle; 355 | end; 356 | 357 | procedure TPkgWaitHandle.unlock; 358 | begin 359 | TMonitor.Exit(FPadlock); 360 | end; 361 | 362 | 363 | { TPkgEvent } 364 | 365 | 366 | 367 | constructor TPkgEvent.Create(AManualResetEvent, AInitialState: boolean); 368 | begin 369 | inherited Create(AManualResetEvent); 370 | FEvent := TEvent.Create(nil, AManualResetEvent, AInitialState, ''); 371 | end; 372 | 373 | destructor TPkgEvent.Destroy; 374 | begin 375 | FreeAndNil(FEvent); 376 | inherited; 377 | end; 378 | 379 | procedure TPkgEvent.resetEvent; 380 | begin 381 | TMonitor.Enter(FPadlock); 382 | try 383 | FEvent.ResetEvent; 384 | finally 385 | TMonitor.Exit(FPadlock); 386 | end; 387 | end; 388 | 389 | procedure TPkgEvent.setEvent; 390 | begin 391 | TMonitor.Enter(FPadlock); 392 | try 393 | FEvent.SetEvent; 394 | if FIsManualReset then 395 | doNotify(1) 396 | else 397 | doNotify(-1); 398 | finally 399 | TMonitor.Exit(FPadlock); 400 | end; 401 | end; 402 | 403 | 404 | 405 | function TPkgEvent.WaitFor(ATimeout: LongWord): TWaitResult; 406 | begin 407 | incWaiters; 408 | try 409 | result := FEvent.WaitFor(ATimeout); 410 | finally 411 | decWaiters; 412 | end; 413 | end; 414 | 415 | { TPkgSemaphore } 416 | 417 | procedure TPkgSemaphore.Acquire; 418 | begin 419 | WaitFor(INFINITE); 420 | end; 421 | 422 | constructor TPkgSemaphore.Create(AInitialCount, AMaximumCount: Integer); 423 | begin 424 | inherited Create(true); 425 | FSemaphore := TSemaphore.Create(nil, AInitialCount, AMaximumCount, ''); 426 | FInitialCount := AInitialCount; 427 | FCurrentCount := AInitialCount; 428 | FMaxCount := AMaximumCount; 429 | end; 430 | 431 | destructor TPkgSemaphore.Destroy; 432 | begin 433 | FreeAndNil(FSemaphore); 434 | inherited; 435 | end; 436 | 437 | procedure TPkgSemaphore.Release; 438 | var 439 | LNotifyCount: integer; 440 | begin 441 | TMonitor.Enter(FPadlock); 442 | try 443 | FSemaphore.Release; 444 | inc(FCurrentCount); 445 | if FWaiterCount = 0 then 446 | begin 447 | LNotifyCount := FMaxCount - FCurrentCount; 448 | doNotify(LNotifyCount); 449 | end; 450 | finally 451 | TMonitor.Exit(FPadlock); 452 | end; 453 | end; 454 | 455 | function TPkgSemaphore.WaitFor(ATimeout: LongWord): TWaitResult; 456 | var 457 | LNotifyCount: integer; 458 | begin 459 | result := wrTimeout; 460 | incWaiters; 461 | try 462 | result := FSemaphore.WaitFor(ATimeout); 463 | finally 464 | TMonitor.Enter(FPadlock); 465 | try 466 | if result = wrSignaled then 467 | dec(FCurrentCount); 468 | decWaiters; 469 | // In case when this is the last waiter 470 | if (result = wrSignaled) AND (FWaiterCount = 0) then 471 | begin 472 | LNotifyCount := FMaxCount - FCurrentCount; 473 | doNotify(LNotifyCount); 474 | end; 475 | finally 476 | TMonitor.Exit(FPadlock); 477 | end; 478 | end; 479 | end; 480 | 481 | { TPkgTaskWaiter } 482 | 483 | constructor TPkgTaskWaiter.Create(ATask: ITask); 484 | begin 485 | inherited Create(true); 486 | FEvent := TLightweightEvent.Create(false); 487 | FTask := ATask; 488 | 489 | FTaskCompletionHandler := 490 | procedure(ATask: ITask) 491 | begin 492 | FEvent.SetEvent; 493 | ATask := nil; 494 | TMonitor.Enter(FPadlock); 495 | try 496 | doNotify(-1); 497 | finally 498 | TMonitor.Exit(FPadlock); 499 | end; 500 | end; 501 | 502 | TPkgTaskHack(TTask(FTask)).AddCompleteEvent(FTaskCompletionHandler); 503 | 504 | end; 505 | 506 | destructor TPkgTaskWaiter.Destroy; 507 | begin 508 | TPkgTaskHack(TTask(FTask)).RemoveCompleteEvent(FTaskCompletionHandler); 509 | FTaskCompletionHandler := nil; 510 | FTask := nil; 511 | FreeAndNil(FEvent); 512 | inherited; 513 | end; 514 | 515 | function TPkgTaskWaiter.WaitFor(ATimeout: LongWord): TWaitResult; 516 | begin 517 | result := wrTimeout; 518 | incWaiters; 519 | try 520 | result := FEvent.WaitFor(ATimeout); 521 | finally 522 | TMonitor.Enter(FPadlock); 523 | try 524 | decWaiters; 525 | // In case when this is the last waiter 526 | if result = wrSignaled then 527 | doNotify(-1); 528 | finally 529 | TMonitor.Exit(FPadlock); 530 | end; 531 | end; 532 | end; 533 | 534 | end. 535 | -------------------------------------------------------------------------------- /Tests/GUI/Threading__test.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {738DE07A-ECA5-4296-BB04-B9AB82F8DAA8} 4 | 16.0 5 | VCL 6 | Threading__test.dpr 7 | True 8 | Debug 9 | Win32 10 | 1 11 | Application 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Base 40 | true 41 | 42 | 43 | System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) 44 | $(BDS)\bin\delphi_PROJECTICON.ico 45 | Threading__test 46 | .\$(Platform)\$(Config) 47 | .\$(Platform)\$(Config) 48 | false 49 | false 50 | false 51 | false 52 | false 53 | 54 | 55 | true 56 | madBasic_;FireDACASADriver;FireDACSqliteDriver;bindcompfmx;DBXSqliteDriver;FireDACPgDriver;FireDACODBCDriver;RESTBackendComponents;emsclientfiredac;fmx;rtl;dbrtl;DbxClientDriver;IndySystem;FireDACCommon;bindcomp;inetdb;tethering;TeeDB;frxe21;frxTee21;DBXInterBaseDriver;Tee;tsc;frx21;log4delphi_D7_PROF;frxDB21;vclFireDAC;madDisAsm_;xmlrtl;svnui;DbxCommonDriver;vclimg;IndyProtocols;dbxcds;DBXMySQLDriver;FireDACCommonDriver;MetropolisUILiveTile;TMSFMXPackPkgDXE7;bindcompdbx;bindengine;vclactnband;vcldb;soaprtl;vcldsnap;bindcompvcl;FMXTee;TeeUI;vclie;fmxFireDAC;FireDACADSDriver;vcltouch;madExcept_;emsclient;CustomIPTransport;vclribbon;VCLRESTComponents;FireDAC;VclSmp;dsnap;Intraweb;fmxase;vcl;IndyCore;IndyIPServer;TMSFMXPackPkgDEDXE7;IndyIPCommon;CloudService;CodeSiteExpressPkg;dsnapcon;FireDACIBDriver;FmxTeeUI;inet;fmxobj;FireDACMySQLDriver;soapmidas;vclx;soapserver;inetdbxpress;svn;ChromiumFMX;dsnapxml;fmxdae;RESTComponents;FireDACMSAccDriver;dbexpress;adortl;IndyIPClient;$(DCC_UsePackage) 57 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 58 | $(BDS)\bin\default_app.manifest 59 | 1033 60 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 61 | 62 | 63 | FireDACASADriver;FireDACSqliteDriver;bindcompfmx;DBXSqliteDriver;FireDACPgDriver;FireDACODBCDriver;RESTBackendComponents;emsclientfiredac;fmx;rtl;dbrtl;DbxClientDriver;IndySystem;FireDACCommon;bindcomp;inetdb;tethering;TeeDB;DBXInterBaseDriver;Tee;vclFireDAC;xmlrtl;DbxCommonDriver;vclimg;IndyProtocols;dbxcds;DBXMySQLDriver;FireDACCommonDriver;MetropolisUILiveTile;bindcompdbx;bindengine;vclactnband;vcldb;soaprtl;vcldsnap;bindcompvcl;FMXTee;TeeUI;vclie;fmxFireDAC;FireDACADSDriver;vcltouch;emsclient;CustomIPTransport;vclribbon;VCLRESTComponents;FireDAC;VclSmp;dsnap;Intraweb;fmxase;vcl;IndyCore;IndyIPServer;IndyIPCommon;CloudService;dsnapcon;FireDACIBDriver;FmxTeeUI;inet;fmxobj;FireDACMySQLDriver;soapmidas;vclx;soapserver;inetdbxpress;dsnapxml;fmxdae;RESTComponents;FireDACMSAccDriver;dbexpress;adortl;IndyIPClient;$(DCC_UsePackage) 64 | 65 | 66 | DEBUG;$(DCC_Define) 67 | true 68 | false 69 | true 70 | true 71 | true 72 | 73 | 74 | 3 75 | 1 76 | false 77 | 78 | 79 | false 80 | RELEASE;$(DCC_Define) 81 | 0 82 | 0 83 | 84 | 85 | 86 | MainSource 87 | 88 | 89 |
Form2
90 | dfm 91 |
92 | 93 | 94 | 95 | 96 | Cfg_2 97 | Base 98 | 99 | 100 | Base 101 | 102 | 103 | Cfg_1 104 | Base 105 | 106 |
107 | 108 | Delphi.Personality.12 109 | Application 110 | 111 | 112 | 113 | Threading__test.dpr 114 | 115 | 116 | 117 | 118 | 119 | Threading__test.exe 120 | true 121 | 122 | 123 | 124 | 125 | 1 126 | .dylib 127 | 128 | 129 | 0 130 | .bpl 131 | 132 | 133 | Contents\MacOS 134 | 1 135 | .dylib 136 | 137 | 138 | 1 139 | .dylib 140 | 141 | 142 | 143 | 144 | 1 145 | .dylib 146 | 147 | 148 | 0 149 | .dll;.bpl 150 | 151 | 152 | Contents\MacOS 153 | 1 154 | .dylib 155 | 156 | 157 | 1 158 | .dylib 159 | 160 | 161 | 162 | 163 | 1 164 | 165 | 166 | 1 167 | 168 | 169 | 170 | 171 | Contents 172 | 1 173 | 174 | 175 | 176 | 177 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 178 | 1 179 | 180 | 181 | 182 | 183 | res\drawable-normal 184 | 1 185 | 186 | 187 | 188 | 189 | library\lib\x86 190 | 1 191 | 192 | 193 | 194 | 195 | 1 196 | 197 | 198 | 1 199 | 200 | 201 | 202 | 203 | Contents 204 | 1 205 | 206 | 207 | 208 | 209 | library\lib\armeabi-v7a 210 | 1 211 | 212 | 213 | 214 | 215 | 1 216 | 217 | 218 | 1 219 | 220 | 221 | 222 | 223 | res\drawable-xlarge 224 | 1 225 | 226 | 227 | 228 | 229 | res\drawable-xhdpi 230 | 1 231 | 232 | 233 | 234 | 235 | 1 236 | 237 | 238 | 1 239 | 240 | 241 | 242 | 243 | res\drawable-xxhdpi 244 | 1 245 | 246 | 247 | 248 | 249 | library\lib\mips 250 | 1 251 | 252 | 253 | 254 | 255 | res\drawable 256 | 1 257 | 258 | 259 | 260 | 261 | Contents\MacOS 262 | 1 263 | 264 | 265 | 1 266 | 267 | 268 | 0 269 | 270 | 271 | 272 | 273 | Contents\MacOS 274 | 1 275 | .framework 276 | 277 | 278 | 0 279 | 280 | 281 | 282 | 283 | res\drawable-small 284 | 1 285 | 286 | 287 | 288 | 289 | 1 290 | 291 | 292 | 293 | 294 | Contents\MacOS 295 | 1 296 | 297 | 298 | 1 299 | 300 | 301 | Contents\MacOS 302 | 0 303 | 304 | 305 | 306 | 307 | classes 308 | 1 309 | 310 | 311 | 312 | 313 | 1 314 | 315 | 316 | 1 317 | 318 | 319 | 320 | 321 | 1 322 | 323 | 324 | 1 325 | 326 | 327 | 328 | 329 | res\drawable 330 | 1 331 | 332 | 333 | 334 | 335 | Contents\Resources 336 | 1 337 | 338 | 339 | 340 | 341 | 1 342 | 343 | 344 | 345 | 346 | 1 347 | 348 | 349 | 1 350 | 351 | 352 | 353 | 354 | 1 355 | 356 | 357 | library\lib\armeabi-v7a 358 | 1 359 | 360 | 361 | 0 362 | 363 | 364 | Contents\MacOS 365 | 1 366 | 367 | 368 | 1 369 | 370 | 371 | 372 | 373 | library\lib\armeabi 374 | 1 375 | 376 | 377 | 378 | 379 | res\drawable-large 380 | 1 381 | 382 | 383 | 384 | 385 | 0 386 | 387 | 388 | 0 389 | 390 | 391 | 0 392 | 393 | 394 | Contents\MacOS 395 | 0 396 | 397 | 398 | 0 399 | 400 | 401 | 402 | 403 | 1 404 | 405 | 406 | 1 407 | 408 | 409 | 410 | 411 | res\drawable-ldpi 412 | 1 413 | 414 | 415 | 416 | 417 | res\values 418 | 1 419 | 420 | 421 | 422 | 423 | 1 424 | 425 | 426 | 1 427 | 428 | 429 | 430 | 431 | res\drawable-mdpi 432 | 1 433 | 434 | 435 | 436 | 437 | res\drawable-hdpi 438 | 1 439 | 440 | 441 | 442 | 443 | 1 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | True 455 | False 456 | 457 | 458 | 12 459 | 460 | 461 | 462 | 463 |
464 | --------------------------------------------------------------------------------