PPL并行编程库02-多任务
一、何为多任务
多任务,即上一个任务尚未执行完毕,下一个任务又启动啦,操作系统中,同时又N个任务在运行,即操作系统的多任务。MSWIDOWS是最早支持多任务的操作系统,POSIX也支持。
二、多任务的并行
2.1、场景
(1)、后台任务按钮:连续点击
(2)、任务中,以TParallel.Join( TArray<TProc> ) 并行加入的方式,同时执行着多个过程函数。
2.1、代码示例:并行的多任务
并行的多任务: 分为,多任务异步执行,和多任务同步执行,两种方式,在下一讲进行讨论。此案例,我们采用的是异步多任务:
procedure TfmxDictionary.getDatabaseFromServer(
DatatableNameOfGetData:string;TotalRecCountOfGetData :Integer;
BeginRecNoOfGetData,RecCountPerPageOfGetData:Integer;
pSql,pSelectKey:string);
var ATask:ITask;
FDJsonDatasets:TFDJsonDatasets;
FDAdaptedDataSet:TFDAdaptedDataSet;
begin
// btnListViewMain_Query.Enabled:=false; // : 见三、共享资源锁的进入和退出的3.2.2、第二种方式:利用UI按钮等的Enabled或Visible的状态进行控制
pCurrtPage1:='1'; pRecsPerPage1:='20';
pKeyMaster:='取物品资料表';
pSqlMaster:=' select top 600 com_id,item_id,item_name,item_type, '
+' item_spec,maintenance_datetime from ctl03001 where com_id=''001'' ';
pKeyDetail:=''; pSqlDetail:='';
pKeyDetaildetail:=''; pSqlDetaildetail:='';
if TMonitor.TryEnter(FDMT_SQLiteMain)=true then
TMonitor.Wait(FDMT_SQLiteMain,0); //: 询问等待脉冲:Pulse,若等到了:说明下面的代码已经把它解锁啦
TMonitor.Enter(FDMT_SQLiteMain,0); // :共享资源加锁
Async.Run<Boolean>(
function :Boolean
begin //:1.在后台线程中执行并未2.和3.回调成功或异常Result结果:
try
//:注意窗体产生时的:FDMT_SQLiteMain.FetchOptions
FDJsonDatasets:=ClientModule1.ServerMethods1Client
.getMatserDetailFDJson(
pCurrtPage,pRecsPerPage,
pKeyMaster,pSqlMaster,
pKeyDetail,pSqlDetail,pKeyDetaildetail,pSqlDetaildetail
);
FDAdaptedDataSet := TFDJSONDataSetsReader.GetListValueByName(
FDJsonDatasets ,'取物品资料表');
//if FDMT_SQLiteMain.Active=true then FDMT_SQLiteMain.Active :=False;
//while FDMT_SQLiteMain.State<>dsInactive do sleep(0);
//:如果内存表在原有基础上追加数据:
//:就不要在其之前关闭内存表,否则就是在更新数据
//:如果连续滑动ListView或执行本点击:
//:任务是后台多线程,找CPUs的空闲,1个任务的线程把它关了,
//:另1个任务的线程又把它打开了
//:关闭内存表:就会有冲突:会提示你:
//:UnEnable this Operation in a Closed Dataset
FDMT_SQLiteMain.AppendData( FDAdaptedDataSet ); //:已含字段定义//FDMT_SQLiteMain.FieldDefs:=FDAdaptedDataSet.FieldDefs;
//:FDAdaptedDataSet需要Rest分页取不同的数据
//:追加给内存表: 关闭按钮的状态来控制
// : FDMT_SQLiteMain:供数据的删改查用
FDMemTemp.CloneCursor(FDAdaptedDataSet); //: FDMemTemp:供UI追加后台数据的加载使用:
//:注意窗体产生时的:FDMemTemp.FetchOptions
if FDMT_SQLiteMain.Active=False then FDMT_SQLiteMain.Open;
while FDMT_SQLiteMain.State=dsInactive do sleep(0);
FDMT_SQLiteMain.IndexFieldNames:='item_id';//'com_id;item_id';
FDMT_SQLiteMain.IndexesActive:=true;//:会触发按索引排序
if FDMT_SQLiteMain.Active=true then FDMT_SQLiteMain.First;
//测试://FDAdaptedDataSet.SaveToFile(
//System.IOUtils.TPath.GetTempPath+'FDAdaptedDataSet.json',TFDStorageFormat.sfJSON );
finally
if (FDMT_SQLiteMain.Active=true) then
Result:=true //:'成功执行任务'
else Result:=false; //:'未成功执行任务'
end;
end,
procedure(const Success:Boolean)
begin //:2.在UI线程中执行(如果1.返回正确的数值)
if Success=true then
begin
if FDMT_SQLiteMain.Active=true
//and (FDMT_SQLiteMain.ChangeCount>0)
then
begin
try
if (FDMT_SQLiteMain.ChangeCount>0) then
UpdateMyListViewItem(ListViewMain); //: 与UI线程对话
finally
Memo1.Lines.Clear; Memo1.Lines.Add('任务执行完毕,字段1:'
+FDMT_SQLiteMain.FieldList[1].FieldName+'记录数:'+IntToStr(FDMT_SQLiteMain.ChangeCount));
if (FDMT_SQLiteMain.Active=true)
and (ListViewMain.Items.Count>0)
then ListViewMain.ScrollTo(0);
// btnListViewMain_Query.Enabled:=true; // : 见三、共享资源锁的进入和退出的3.2.2、第二种方式:利用UI按钮等的Enabled或Visible的状态进行控制
TMonitor.Exit(FDMT_SQLiteMain); // :共享资源解锁
end;
end;
end;
end,
procedure(const Ex:Exception)
begin //:3.处理1.中的异常
if Ex.ClassType=EMonitorLockException then
ShowMessage('别点得那么猛:' + sLineBreak + '还在加载上一组数据!') // :未锁定共享资源的异常:上面解决掉,不可能出现
else ShowMessage('出错啦:' + sLineBreak + Ex.Message); // :获取数据异常
// btnListViewMain_Query.Enabled:=true; // : 见三、共享资源锁的进入和退出的3.2.2、第二种方式:利用UI按钮等的Enabled或Visible的状态进行控制
TMonitor.Exit(FDMT_SQLiteMain); // :共享资源解锁
end
);
end ;
unit AsyncTask;
interface
uses
System.SysUtils, System.Threading;
type
TAsyncBackgroundTask<T> = reference to function: T;
TAsyncSuccessCallback<T> = reference to procedure(
const TaskResult:T );
TAsyncErrorCallback = reference to procedure(
const E: Exception );
TAsyncDefaultErrorCallback = reference to procedure(
const E: Exception;
const ExptAddress: Pointer );
Async = class sealed
public
class function Run<T>(
Task: TAsyncBackgroundTask<T>;
Success: TAsyncSuccessCallback<T>;
Error: TAsyncErrorCallback = nil ):ITask;
end;
var
DefaultTaskErrorHandler: TAsyncDefaultErrorCallback = nil;
implementation
uses
System.Classes;
{ Async }
class function Async.Run<T>(
Task: TAsyncBackgroundTask<T>;
Success: TAsyncSuccessCallback<T>;
Error: TAsyncErrorCallback ) :ITask;
var
LRes: T;
begin
Result := TTask.Run(
procedure
var
Ex: Pointer;
ExceptionAddress: Pointer;
begin
Ex := nil;
try
LRes := Task(); //:1.后台任务的方法:返回泛型T
if Assigned(Success) then
//:如果指定了参数Success的类型及其参照reference
begin
TThread.Queue(nil, //:Queue在主线程(UI线程)中运行
procedure
begin
Success(LRes); //:2.队列化-后台任务的成功回调的函数:传入这个泛型T
end);
end;
except
Ex := AcquireExceptionObject;
//:请求系统返回异常对象:system.AcquireExceptionObject
ExceptionAddress := ExceptAddr;
//:请求系统返回异常地址:system.ExceptAddr
TThread.Queue(nil, //:Queue在主线程(UI线程)中运行
procedure
var
LCurrException: Exception;
//:系统异常类Exception实例变量
begin
LCurrException := Exception(Ex);
//:系统异常类获取某个异常对象
try
if Assigned(Error) then
Error(LCurrException)
//:3.1.队列化-后台任务的异常回调的函数:传入这个异常LCurrException
else
DefaultTaskErrorHandler(
LCurrException,
ExceptionAddress );
//:3.2.队列化-后台任务的默认异常回调的函数:传入这个异常LCurrException及其地址ExceptionAddress
finally
FreeAndNil(LCurrException);
end;
end);
end;
end);
end;
initialization
DefaultTaskErrorHandler :=
procedure(
const E: Exception;
const ExceptionAddress: Pointer )
begin
ShowException(E, ExceptionAddress);
end;
end.
三、多任务并行或线程执行的注意事项
3.1、共享资源
多个任务在并行,或者线程在执行。此时,有可能别的任务或线程也同时正在操作某个资源,这个资源,被看成是共享资源。比如:内存表,比如从内存表加载数据的TListView或TListBox等等。
此时需要任务或线程对共享资源进行加锁运行后解锁,任务或线程在线程池中,以队列的方式按先进先出(FIFO : First-In-First-Out)原则 ( 当然任务是多线程的,其线程池会主动找CPUs或GPUs的idle空闲,优先执行)使用这些共享资源。
3.2、共享资源锁的进入和退出
3.2.1、第一种方式:运用锁的脉冲进行控制
// ......任务开始前(最佳的处理方式:连续处理方式:完全不阻塞的方式):
if TMonitor.TryEnter ( LObject1 ) then // : 如果LObject1要再次进入锁定
TMonitor.WaitFor ( LObject1,0 ) ; // : 等待LObject1(解锁)上一次的脉冲信号(脉冲:有锁的任何变化)
// ......开始任务代码
TMonitor.Enter ( LObject1 ,0 ) ; //: 锁定1
TMonitor.Enter ( LObject2 ,0 ) ; //: 锁定2
// ......你对共享资源的处理
// 共享资源可以嵌套:后进先出 : LIFO (Last-In-First-Out) ,不采用先进先出FIFO (First-In-First-Out)
TMonitor.Exit ( LObject2 ) ; //: 解锁2
TMonitor.Exit ( LObject1 ) ; //: 解锁1
// ......任务代码结束
3.2.2、第二种方式:利用UI按钮等的Enabled或Visible的状态进行控制
// ......任务开始前(非连续处理方式:不阻塞UI,但UI按钮的状态会等待上次任务的结束后,方可再次点击执行下一次任务):
btnListViewMain_Query.Enabled:=false;
// ......开始任务代码
TMonitor.Enter ( LObject1 ,0 ) ; //: 锁定1
TMonitor.Enter ( LObject2 ,0 ) ; //: 锁定2
// ......你对共享资源的处理
// 共享资源可以嵌套:后进先出 : LIFO ,不采用先进先出PIFO
TMonitor.Exit ( LObject2 ) ; //: 解锁2
TMonitor.Exit ( LObject1 ) ; //: 解锁1
btnListViewMain_Query.Enabled:=true;
// ......任务代码结束