3
votes

I have never asked questions in any community as I always solved problems by myself or could find them online. But with this one I came to dead end and need Help! To make it very clear – I converted a simple app, found elsewhere to make it use a Tthread object. The idea is simple – the app checks online using webservice, through THTTPRIO component, weather and put the results in Memo1 lines.

Clicking on Button1 we get it done in standard way – using THTTPRIO put on the Form1 (it's called here htt as in original app) and using main and only thread.

procedure TForm1.Button1Click(Sender: TObject);
var
wf:WeatherForecasts;
res:ArrayOfWeatherData;
i:integer;
begin
    wf:=(htt as WeatherForecastSoap).GetWeatherByPlaceName(edit1.Text);
    if wf.PlaceName<> '' then
    res:=wf.Details;
    memo1.Lines.Add('The min and max temps in Fahrenheit is:');
    memo1.Lines.Add(' ');
    for i:= 0 to high(res) do
    begin
        memo1.Lines.Add(res[i].Day+'   -   '+ ' Max Temp. Fahr: '+res[i].MaxTemperatureF+'   -   '+'Min Temp Fahr: '+res[i].MinTemperatureF);
    end
end;

Clicking on Button2 – we use class TThread

procedure TForm1.Button2Click(Sender: TObject);
var WFThread:WeatherThread;
begin
  WFThread := WeatherThread.Create (True);
  WFThread.FreeOnTerminate := True;
  WFThread.Place := Edit1.Text;
  WFThread.Resume;
end;

In Execute procedure in WeatherThread1 unit I put this code:

procedure WeatherThread.Execute;
begin
  { Place thread code here }
  GetForecast;
  Synchronize (ShowWeather);
end;

...and the GetForecast code:

procedure WeatherThread.GetForecast;
var
    HTTPRIO: THTTPRIO;
    wf:WeatherForecasts;
    res:ArrayOfWeatherData;
    i:integer;
begin
    HTTPRIO := THTTPRIO.Create(nil);
    HTTPRIO.URL := 'http://www.webservicex.net/WeatherForecast.asmx';
    HTTPRIO.WSDLLocation := 'http://www.webservicex.net/WeatherForecast.asmx?WSDL';
    HTTPRIO.Service := 'WeatherForecast';
    HTTPRIO.Port := 'WeatherForecastSoap';

    wf:=(HTTPRIO as WeatherForecastSoap).GetWeatherByPlaceName(Place);

    if Lines=nil then Lines:=TStringList.Create;

    if wf.PlaceName<> '' then
    res:=wf.Details;
    Lines.Clear;
        for i:= 0 to high(res) do
    begin
        Lines.Add(res[i].Day+'   -   '+ ' Max Temp. Fahr: '+res[i].MaxTemperatureF+'   -   '+'Min Temp Fahr: '+res[i].MinTemperatureF);
    end;
end;

Procedure ShowWeather shows results in Form1.Memo1. And now there is a problem: In main thread, clicking Button1, everything works fine. But of course when HTTPRIO component communicates – it freezes the form.

With Button2 I put the code in separate thread but it does NOT WANT TO WORK! Something strange happens. When I start application – and click Button2, there is an error when using HTTPRIO component. But it works for a while when I click FIRST Button1 and AFTER THAT Button2 (but it works for a while, 5-7 clicks only). I suppose I do something wrong but cannot figure out where the problem is and how to solve it. It looks like the code in threaded unit is not thread-safe, but it should be. Please help how to make HTTPRIO work in a thread!!!

You can find zipped full code here.

3
You need to be more specific when you say something is "strange" and "an error" occurs. What error?Rob Kennedy
You do realize that you have a memory leak in your WeatherTread.GetForecast? You create but never free the THTTPRIO instance.Marjan Venema
That's not true, @Marjan. When THTTPRIO is created without an owner, it uses interface reference counting to manage its lifetime. Type-casting it to WeatherForecastSoap creates a temporary interface reference that gets released at the end of the function.Rob Kennedy
Is all the thread code here? I don't see a call to CoInitialize. Delphi handles the call while initializing 'ComObj.pas' for the main thread but for a separate thread you'd call it yourself. BTW, are you behind a proxy?Sertac Akyuz
@Rob Kennedy: Thanks for clearing that up. I was shooting from the hip I guess...Marjan Venema

3 Answers

4
votes

When I run your code in Delphi 2007, madExcept shows an exception CoInitialize has not been called.
After adding the call to CoInitialize in the execute method, the webservice gets called without problems.

Possible fix

procedure TWeatherThread.Execute;
begin
  CoInitialize(nil);
  try
     ...
  finally
    CoUninitialize;
  end;
end;
1
votes

A long shot, but I'm missing calls to Synchronize here:

You should never update your GUI directly from your thread code.

You should embed those calls inside a method, and call that method using the TThread.Synchronize method for this.

Delphi about has a nice demo on this.
Since Delphi 4, it includes a demo called sortthds.pas in the ...\demos\threads subdirectory that shows the same.

--jeroen

0
votes

You may be clouding the issue by doing the dynamic RIO creation (RIO objects have a strange lifetime) and threading together, and comparing that outcome to the straightforward Button1. I'd make another button that calls GetForecast without threads. See if that works. If it bombs, then your problem isn't threading.