今天新增了一个使用WeatherFetch的例子,简单的演示了如何使用TQJobGroup和TQForJobs来并行从百度天气获取数据。本文为您略加解读相关代码,以便让大家更易于理解这一示例。这个例子主要演示了:
1、QWorker 中使用 TQJobGroup 定制顺序执行作业流程。
2、QWorker 中 TQForJobs 中 For 并行编程的使用。
3、使用 Windows WinINet API 来访问网站的方式。
【注意】
1、受百度API的限制,这个例子只能每天查询5000次天气信息,超过后将无法获取到正确的数据。
2、如果你使用代理上网,请修改InternetOpen函数的参数,将代理服务器参数设置好,否则将同样获取不到天气信息。
首先,本示例是一个两步分组作业:
1、使用For并行来从百度获取147个城市的天气预报信息。
2、完成后,在主线程中报告作业花费的时间。
接下来,我们来先看分组作业的代码:
procedure TForm4.Button1Click(Sender: TObject); var AGroup: TQJobGroup; begin Button1.Enabled := False; Button1.Caption := '获取中...'; Memo1.Lines.Clear; AGroup := TQJobGroup.Create(True); AGroup.Prepare; AGroup.Add(DoFetchWeathers, nil, False); AGroup.Add(DoShowResult, Pointer(GetTickCount), True); AGroup.Run(); AGroup.MsgWaitFor(); FreeObject(AGroup); Button1.Caption := '开始'; Button1.Enabled := True; end;
在这个函数中,我们做了几件事:
(1)先将 Button1 的按钮给禁用,以避免多次点击。
(2)使用 TQJobGroup.Create(True) 创建了一个串行化的作业序列,当 TQJobGroup 的构造函数参数为True时,添加到分组中的作业将严格按照作业加入的先后顺序而不是并行执行,因为我们第二步要等待第一步完成才需要执行,所以我们参数被设置为 True 。我们在设置串行化执行的作业时,一定要注意这一点。
(3)从 AGroup.Prepare 开始到 AGroup.MsgWaitFo r完成作业添加并执行等待的过程,这其中,Prepare和Run并不是必需的,只是在批量添加作业时,这样做更高效一些。这里我们使用了MsgWaitFor而不是WaitFor以避免主界面死掉,因为作业的第二步是在主线程中执行,如果你使用WaitFor会由于主线程死掉而陷入死锁。这一点在等待作业完成时一定要注意。
(4)作业完成的清理工作,释放创建的分组对象,恢复按钮状态。
在上面的代码中,我们安排了两个作业,DoFetchWeathers 和 DoShowResult,我们现在来分别看它们的源码:
procedure TForm4.DoFetchWeathers(AJob: PQJob); begin TQForJobs.For(0, 147, FetchCityWeather); end; procedure TForm4.DoShowResult(AJob: PQJob); begin ShowMessage('获取天气数据完成,用时' + Rolluptime((GetTickCount - Cardinal(AJob.Data)) div 1000)); end;
很简单, DoFetchWeather使用For并行计算来调用FetchCityWeather函数来获取这148个城市的天气数据,DoShowResult 直接显示了获取天气数据的用时。
我们进一步看下FetchCityWeather函数的代码:
procedure TForm4.FetchCityWeather(ALoopMgr: TQForJobs; AJob: PQJob; AIndex: NativeInt); var AConn, AHttp: HINTERNET; AReaded: Cardinal; S: TMemoryStream; AJson: TQJson; AWeather: PString; ABuf: array [0 .. 65535] of Byte; function EncodeUtf8Url(S: QStringW): QStringW; var U8: QStringA; p: PQCharA; begin U8 := QString.Utf8Encode(S); p := PQCharA(U8); SetLength(Result, 0); while p^ <> 0 do begin if p^ < 128 then begin Result := Result + Char(p^); Inc(p); end else begin while p^ >= 128 do begin Result := Result + '%' + IntToHex(p^, 2); Inc(p); end; end; end; end; begin AConn := InternetOpen(nil, INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0); if AConn <> nil then begin AHttp := InternetOpenUrl(AConn, PChar('http://api.map.baidu.com/telematics/v3/weather?location=' + EncodeUtf8Url(Cities[AIndex]) + '&output=json&ak='+BaiduAK), nil, 0, 0, 0); if AHttp <> nil then begin S := TMemoryStream.Create; try while InternetReadFile(AHttp, @ABuf[0], 65536, AReaded) and (AReaded > 0) do S.WriteBuffer(ABuf[0], AReaded); S.Position := 0; AJson := TQJson.Create; try if AJson.TryParse(LoadTextW(S)) then begin // 更新天气信息 New(AWeather); AWeather^ := IntToStr(AIndex) + '-' + Cities[AIndex] + '天气:'#13#10 + AJson.AsJson; Workers.Post(DoLogWeather, AWeather, True); end; finally FreeObject(AJson); end; finally FreeObject(S); InternetCloseHandle(AHttp); end; end; InternetCloseHandle(AConn); end; end;
一个基本标准的使用 WinInet 函数发送 HTTP GET请求并获取结果的过程,这里因为是演示的例子,我没太处理出错的问题。获取到结果后,我们解析收到的 Json 格式天气数据,然后触发了一个主线程异步作业 DoLogWeather 来将得到的Json结果记录到 Memo 中显示出来。这里我没有特意保证AWeather释放的问题,实际上如果在作业未完成前释放可能会存在内存泄漏,更理想的方式是直接传递 AJson 对象,然后设置 jdfFreeAsObject 。
【注意】演示程序中用到的BaiduAK是QDAC申请的百度开发者应用AK,有5000次访问的配置限制,如果运行演示的用户过多,很快将超过配额,造成无法访问。
最后再看下 DoLogWeather 的源码吧,没啥玩意。
procedure TForm4.DoLogWeather(AJob: PQJob); begin Memo1.Lines.Add(PString(AJob.Data)^); Dispose(PString(AJob.Data)); end;
好了,整个例子到些结束,我们看一个程序执行的效果吧:
【完整源码下载】 百度网盘