PPL并行编程库03-单任务、异步多任务、同步多任务
一、概念:单任务、异步多任务、同步多任务
所谓单任务:在任务的生命周期中,只有1个任务在运行,共享资源被它所独占。否则,在任务的生命周期中,又加入了其它的N个任务,共享资源可能被所有任务所共享,成为多任务在运行。
所谓异步多任务:假如N个任务,对应N个过程函数,这N个过程UI并不同时发出执行指令,它们执行后,后台也被按照队列执行。比如,这种场景:我录入完毕 “ 销售开单 ”执行审核任务,审核任务包含几个子任务(几个过程):1、先同步1个多任务,检查从表各行次库存是否足够,若存在库存不足,将最终结果一次性通知用户,提请其修正;2、在1做完(无论结果如何)的基础之上,启动一个过程函数,将1、中合计的总数量及总金额更新主表;3、如果不存在任何库存不足,将从表单据中所开具的产品,再同步1个多任务,一次性下它们的库存。整个3步,代表了三大过程,它们并非同步在并行,而是存在先后次序的在执行,我们可以理解,代表这三大过程的总任务,为异步多任务。
所谓同步多任务:假如N个任务,对应N个过程函数,这N个过程UI连续发出执行指令,或者单任务中,被连续执行了N个同步的或称并行的过程函数。比如,这种场景:用户一进入APP或当APP处于后台执行模式,我需要将常用的基础资料一次性在后台线程中,一 一并行的将它们的数据从Rest服务器取出并赋值给本地数据库,以供将来它们需要被调用时,随时取用,从而提升UE用户体验。再比如:上述异步多任务中,第1、和第3、步,为了提升执行效率,我们分别让它们的各行次逐行的产品的判断计算和库存更新的子任务,让这些子任务同时并行,可理解为同步多任务。
二、单任务的实现与状态控制
(一)、单任务可立即执行,也可被准备好再执行:
2.1、立即执行模式:
TTask.Run (
procedure
//var ...; // :可代变量
begin
//:1、长时计算和操作,比如Rest获取服务器数据 (可写成独立的方法function或函数procedure,但其不能再有Sender等事件通知参数)
sleep( 15000 ) ; //:模拟你的长时计算和操作
// : 2、写下面(二)、与UI线程进行交互的代码(含UI事件通知)
// ......
end ); //:返回值: ITask (TTask的接口)
2.2、准备好再执行的模式:
var LTask : ITask ;
LTask := TTask.Create (
procedure
//var ...; // :可代变量
begin
//:1、长时计算和操作,比如Rest获取服务器数据 (可写成独立的方法function或函数procedure,但其不能再有Sender等事件通知参数)
sleep( 15000 ) ; //:模拟你的长时计算和操作
// : 2、写下面(二)、与UI线程进行交互的代码(含UI事件通知)
// ......
end ) ; //:返回值: ITask类型的LTask (TTask的接口)
LTask.Start;
(二)、单任务中与UI交互的方式:同步或队列
1、线程同步:
TThread.Synchronize ( nil,
procedure
begin
TDialogService.MessageDialog('执行完',TMsgDlgType.mtInformation,[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK,-1,nil );
// ......其它UI操作及对获取的数据进行处理
end );
2、线程队列:
TThread.Queue ( nil,
procedure
begin
TDialogService.MessageDialog('执行完',TMsgDlgType.mtInformation,[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK,-1,nil );
// ......其它UI操作及对获取的数据进行处理
end);
(三)、单任务的状态通知与控制
可直接使用单元的全局TEvent事件通知变量:
private
AEvent:TEvent; //: 单任务的全局事件通知类变量
{ Private declarations }
implementation
procedure TfmxDictionary.getDatabaseFromServer(
DatatableNameOfGetData:string;TotalRecCountOfGetData :Integer;
BeginRecNoOfGetData,RecCountPerPageOfGetData:Integer;
pSql,pSelectKey:string);
var ATask:ITask; //:单任务:接口变量
FDJsonDatasets:TFDJsonDatasets;
FDAdaptedDataSet:TFDAdaptedDataSet;
var pCurrtPage1, pRecsPerPage1,
pKeyMaster, pSqlMaster, pKeyDetail, pSqlDetail,
pKeyDetaildetail, pSqlDetaildetail :string;
begin
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:='';
btnListViewMain_Query.Enabled:=false;
AEvent:=TEvent.Create; //:创建事件对象
//创建并启动(多线程池化的)单任务:多核多处理器并行线程(多路CPU或GPU同时工作,或单处理器多核同步)
ATask:=TTask.Create(
procedure
var AtestFieldDefsCircle:Integer;
begin
//Rest长操作:
TMonitor.Enter(FDMT_SQLiteMain,0); //:共享资源加锁 //多核多处理器环境,经常用uses system;
try
try
try
FDJsonDatasets:=ClientModule1.ServerMethods1Client
.getMatserDetailFDJson(
pCurrtPage,pRecsPerPage,
pKeyMaster,pSqlMaster,
pKeyDetail,pSqlDetail,pKeyDetaildetail,pSqlDetaildetail
);
//赋值并打开数据集:
FDAdaptedDataSet := TFDJSONDataSetsReader.GetListValueByName(
FDJsonDatasets ,'取物品资料表');
while not (FDAdaptedDataSet.RecordCount>0) do sleep(0);
FDMT_SQLiteMain.AppendData( FDAdaptedDataSet ); //UI所需内存表获得数据
while FDMT_SQLiteMain.State=dsInactive do sleep(0);
FDMT_SQLiteMain.Open;
if (FDMT_SQLiteMain.Active=true) then
FDMT_SQLiteMain.First;
// : FDMT_SQLiteMain:供数据的删改查用
FDMemTemp.CloneCursor(FDAdaptedDataSet); //: FDMemTemp:供UI追加后台数据的加载使用:
//:注意窗体产生时的:FDMemTemp.FetchOptions
finally
// 与UI线程对话: TListView加载数据或重新加载数据:ListViewMain:
TThread.Synchronize(nil, // :同步模式 或// TThread.Queue(nil, // :队列模式
procedure
begin
if (FDMT_SQLiteMain.Active=true) then
begin
if FDMT_SQLiteMain.RecordCount>0 then
begin
UpdateMyListViewItem(ListViewMain);
ListViewMain.ScrollTo(0);
end ;
end ;
end );
end ;
//:上面是单任务的具体内容!
except //万一异常,代码很重要 :
TMonitor.Exit(FDMT_SQLiteMain); // : 共享资源解锁
btnListViewMain_Query.Enabled:=true; //:允许再次点击任务按钮
AEvent.Free; //:出现异常释放:事件!
raise Exception.Create('任务出错或取消任务啦');
exit; //:出现异常就不要再往下执行了!
end;
finally
TMonitor.Exit(FDMT_SQLiteMain); //监视器解锁共享资源
btnListViewMain_Query.Enabled:=true; //:允许再次点击任务按钮
AEvent.SetEvent;//:事件通知:任务结束!
//AEvent.Free;
//:切忌:将事件通知变量在try内释放AEvent.Free;
end;
//事件总线AEvent.SetEvent后:确认任务是否完成:
//Assert(AEvent.WaitFor(15000)>TWaitResult.wrSignaled);
//:调试时:确认任务结束时长的信息标记,即AEvent.SetEvent,然后:
//AEvent.Free; //:确认收到任务结束的信号标记即AEvent.SetEvent后再:释放事件总线对象
// : 注意:如果你不在任务中释放AEvent事件通知,而在UI窗体的OnDestroy中释放,则你可以在UI线程的其它事件中调用其状态,来判断你的这个单任务的执行状态!方法如下:
// if AEvent.WaitFor(0) = TWaitResult.wrSignaled then ShowMessage('这个单任务是完成了的') ; //:只要上面代码中标记有: AEvent.SetEvent; //: 代表事件通知:任务结束!
// if AEvent.WaitFor(0) = TWaitResult.wrTimeout then ShowMessage('此单任务尚未完成') ;
// : 调用方法见下面:procedure TfmxDictionary.AOtherAnyEvent ( Sender:TObject );
end );
//多任务管理接口://arrayTasks[0]:=ATask;//TTask.WaitForAll(arrayTasks);
//if arrayTasks[0].Status=TTaskStatus.Running then
//arrayTasks[0].Wait(INFINITE);
//:上述是不妥的,可使用任务并行加入的方式在任务运行中执行多个过程:
//:ATask:= TParallel.Join(TArray<TProc>);
ATask.Start;
end ;
procedure TfmxDictionary.AOtherAnyEvent ( Sender:TObject );
begin
if AEvent.WaitFor(0) = TWaitResult.wrSignaled then
begin
ShowMessage('这个单任务是完成了的') ; //:只要上面代码中标记有: AEvent.SetEvent; //: 代表事件通知:任务结束!
// ......你的其它需要顺序处理代码
end;
if AEvent.WaitFor(0) = TWaitResult.wrTimeout then
begin
ShowMessage('此单任务尚未完成') ; exit ;
end;
end;
procedure TfmxDictionary.FormDestroy(Sender: TObject);
begin
AEvent.Free;
end;
三、异步多任务的实现与状态控制
3.1、异步单任务:
详见:《PPL并行编程库02-多任务》 https://blog.csdn.net/pulledup/article/details/102068352 中的单元文件unit AsyncTask;
句法:
var ResultITask :T; //: 任务的返回值
var ExceptTask :Exception; //: 任务的异常处理变量
Async.Run<T> (
function :T //:后台线程中运行的
begin
// ResultITask := ......; //:任务的后台执行代码
Result := ResultITask ;
end ,
procedure ( ResultITask ) //:UI线程中运行的
begin
ShowMessage(‘ 任务完毕 ’); //:任务的UI线程通知和数据处理:
//....写任务的UI动作
end ,
procedure ( ExceptTask ) //:UI线程中运行的
begin
ShowMessage (ExceptTask1.Message);
//....任务的异常处理
end
) ; // :任务结束
3.2、异步多任务
3.2.1、实现方式1:异步单任务的成功回调的迭代嵌套
var ResultITask1, ResultITask2, ResultITaskN :T; //: 多个任务的返回值
var ExceptTask1, ExceptTask2, ExceptTaskN :Exception; //: 多个任务的异常处理变量
//开始任务1:
Async.Run<T> (
function :T //:后台线程中运行的
begin
// ResultITask1 := ......; //:任务1的后台执行代码
Result := ResultITask1 ;
end ,
procedure ( ResultITask1 ) //:UI线程中运行的
begin
ShowMessage(‘ 任务1完毕 ’); //:任务1的UI线程通知和数据处理:
//....写任务1的UI动作
//迭代嵌套任务2:
Async.Run<T> (
function :T //:后台线程中运行的
begin
// ResultITask2 := ......; //:任务2的后台执行代码
Result := ResultITask2 ;
end ,
procedure ( ResultITask2 ) //:UI线程中运行的
begin
ShowMessage(‘ 任务2完毕 ’); //:任务2的UI线程通知和数据处理:
//....写任务2的UI动作
// 任务N迭代嵌套:
// ........任务N的代码
end,
procedure ( ExceptTask2 ) //:UI线程中运行的
begin
ShowMessage (ExceptTask2.Message);
//....任务2的异常处理
end
end ) ; // :任务2结束
end ,
procedure ( ExceptTask1 ) //:UI线程中运行的
begin
ShowMessage (ExceptTask1.Message);
//....任务1的异常处理
end
) ; // :任务1结束
3.2.2、实现方式2:TTask.Future<T>: 任务.未来
该方式在下一讲单独介绍。
四、同步多任务的实现与状态控制
4.1、实现方式1:多个单任务连续执行
var ITask1, ITask2, ITaskN : ITask ; //: 任务的接口类型变量
begin
ITask1 := TTask.Run (
procedure //var .. ;
begin
// ITask1的执行代码 : ......
TThread.Queue ( nil, procedure begin //:队列模式 // 或同步模式: // TThread. Synchronize ( nil, procedure begin
// ITask1与UI的对话代码......
end ) ;
end;
) ;
//......ITask2、ITask3......的执行
ITaskN := TTask.Run (
procedure //var .. ;
begin
// ITaskN的执行代码 : ......
TThread.Queue ( nil, procedure begin //:队列模式 // 或同步模式: // TThread. Synchronize ( nil, procedure begin
// ITaskN与UI的对话代码......
end ) ;
end;
) ;
end ;
4.2、实现方式2:单任务的运行中执行多个匿名过程函数的数组:并行.加入
TParallel.Join ( TArray<TProc> )
句法:
TTask.Run (
procedure //var .. ;
begin
// ITaskN的执行代码 : ......
TParallel.Join (
[ //:数组开始
procedure
begin
// 匿名过程函数1的代码......
end , // 数组分割逗号
// 匿名过程函数2、3、......
procedure
begin
// 匿名过程函数N的代码......
end
] //:数组结束
) ;
end
) ;
专业实现方法和步骤:
var LProcs: TArray<TProc>; // :过程函数的数组
LTask : ITask;// : 并行多任务的接口:供并行.加入TParallel.Join ( TArray<TProc> ) 赋值使用
begin
SetLength ( LProcs,20 ) ; //: 1、设置过程函数数组的长度20:
LAssigned ( LProcs ); //:2、你写的为过程函数数组LProcs赋值的过程或方法
TTask.Run ( //: 3、运行1个任务
procedure //var .. ;
begin
// 4、在运行中并行.加入: 过程函数的数组
LTask := TParallel.Join ( LProcs ) ;
LTask.WaiFor ( Infinity ) ; //:5、等待这些并行过程的执行,直到它们完成为止!
TThread.Queue ( nil, procedure begin //:队列模式 // 或同步模式: // TThread. Synchronize ( nil, procedure begin
// 6、通知UI线程:TTask和LTask全部执行完毕(过程函数的数组中的过程全部处理结束)......
// 7、在UI线程中做你的数据操作等......
end ) ;
end );
end ;