我们知道,后台作业不能直接访问和操作用户界面元素。实际上,所谓的直接访问并不是绝对的,但属性这个东西你读时,可能执行的是一个读函数,它是否内部进行了一些不安全的写操作,有时候是叫不准的,那么,显然不在后台作业中访问更安全。
那么,我们如果需要和前面的用户界面交互该怎么做?
在进一步讨论之前,我们首先要明白和主线程进行交互,实际上存在同步和异步两种情况的:
(1)、异步的交互适合通知类型的情况,它只是将要交互的内容发往主线程,由主线程根据相应的参数进行处理。
(2)、同步的交互适合于需要等待主线程处理结果的情况,它需要将交互的内容发往主线程后,由主线程返回结果给自己。
那么,同步的异步真的是绝对的吗?当然不是,实际上同步的操作可以很容易的转换为异步的操作。比如,我们异步通知主线程后,然后由主线程执行操作完后,再触发一个后台作业的异步执行,那效果和同步实际上并没有什么本质的区别。只是同步的编程模型在许多时候,更容易理解,代码的可读性更好。但反过来,同步的编程模型必需等待结果返回,在此过程中,当前线程必需等待而不能处理其它任务,从而影响处理效率。所以,我们一般推荐都是异步来进行处理的。
在 QWorker 中,我们推荐的基本处理流程是:
(1)、后台作业投寄主线程作业后退出;
(2)、主线程作业完成后,触发新的后台作业后退出;
(3)、新的后台作业进行进一步处理;
在这些步骤中,参数的传递可以通过AData成员传递,也可以通过将作业过程写成一个类的成员,然后直接访问类的成员变量来解决(类似于下面的分组作业处理)。
但是,作为一个有良心的开发者,我还是希望你了解的更多,所以我们进一步来阐述这一问题。
上面的步骤如果多的话,你可能觉得自己处理有点麻烦,所以 QWorker 为你提供了作业分组对象:TQJobGroup。你只需要创建一个顺序执行的作业分组,然后将步骤依次加入到分组,然后调用 TQJobGroup.Run 就好了,TQJobGroup 会依次执行作业,而不需要你再手工投寄。当然了,作业的公共参数也就不能通过作业的 AData 成员传递了,而应该将其赋值给 TQJobGroup 的 Tag 标签,以便所有的作业共享,或者你创建一个类,将所有的公共参数和作业处理函数都做为这个类的成员,这样子就两全齐美了,不用受标签的 Tag 指针的限制了。大概示意如下:
【Delphi】
TMyGroupJobs=class protected //自己的变量声明,然后声明作业分组对象 FGroup:TQJobGroup; //声明作业处理步骤函数 procedure DoStep_1(AJob:PQJob); ... procedure DoStep_n(AJob:PQJob); //用于响应FGroup.AfterDone事件,在所有作业完成时自动释放 proceduer DoJobsDone(ASender:TObject); public constructor Create;overload; destructor Destroy;override; end; ... constructor TMyGroupJobs.Create; begin inherited; ... FGroup:=TQJobGroup.Create(True); FGroup.AfterDone:=DoJobsDone; FGroup.Prepare; FGroup.Add(DoStep_1.. FGroup.Run; ... end; constructor TMyGroupJobs.Destory; begin FGroup.Cancel; FGroup.Free; inherited; end; proceduer TMyGroupJobs.DoJobsDone(ASender:TObject); begin FreeObject(Self); end; ... AJobs:=TMyGroupJobs.Create; ....
【C++ Builder】
class TMyGroupJobs { protected: //自己的变量声明,然后声明作业分组对象 TQJobGroup *FGroup; //声明作业处理步骤函数 void __fastcall DoStep_1(PQJob AJob); ... void __fastcall DoStep_n(PQJob AJob); //用于响应FGroup.AfterDone事件,在所有作业完成时自动释放 void __fastcall DoJobsDone(TObject *ASender) { delete self; } public: __fastcall TMyGroupJobs() { ... FGroup=new TQJobGroup(true); FGroup->AfterDone=DoJobsDone; FGroup->Prepare(); FGroup->Add(DoStep_1,... FGroup->Run(); } __fastcall ~TMyGroupJobs() { ... FGroup->Cancel(); delete FGroup; } }; ... AJobs=new TMyGroupJobs; ....
只需要将在主线程中运行的作业投递到主线程去执行就好了,剩下的就是等待它完成了。什么,内存泄露?AJobs在你保存下,退出前自己释放下不就好了,这个不需要我告诉你吧。
好了,虽然说的是 QWorker,但我不介意提一下原生的方式:
(1)、PostMessage 发送通知消息到主线程,然后在主线程中响应消息处理。
(2)、SendMessage 发送通知到主线程,然后等待主线程中完成处理并返回结果。
无论 PostMessage 还是 SendMessage 函数,它们都是线程安全的,可以放心的投递而不用担心。唯一需要担心的是 PostMessage 的消息在特定的情况下,可能会得不到执行的机会(窗口在处理该消息之前被重建或销毁,原来的句柄就无效了)。当然,本着一切自愿,风险自担的原则,请大家放心使用。对了,提示下 FMX 平台下没有 PostMessage,但我们为您提供了一个模拟函数,参考主题:在FMX中实现PostMessage的方法,这里就不啰嗦了。
【注】QWorker 实际上信号作业也可以在主线程和后台线程之间调度作业完成交互,参数的共享也只需要象前述一样解决就好。