Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

COM backgroundprocess challenge

Status
Not open for further replies.

Gerrit Broekhuis

Programmer
Aug 16, 2004
316
NL
Chris Miller has been very helpfull writing thread 184-1820019: Multiprocessing with a COM Server. I intend to use this to call ChatGPT.

In the main program I added a property "Payload", called by this (changed) procedure:

Code:
 Hidden Procedure ExecuteScript(HWnd As Integer, Msg As Integer, wParam As Integer, Lparam As Integer)
   	  LOCAL lcPayload
   	  lcPayload = ALLTRIM(this.Payload)
      This.LastReturnValue = Execscript(This.Script)
      && This.Reply = loHTTP.responseText
      This.Executing = .F.

      Return 0
   Endproc

I'm using this background program to access ChatGPT we talked about in another thread last week.

I fill the property payload in my calling program like this:

Code:
 * Create the request payload
TEXT To goBackgroundprocess.Payload Textmerge NOSHOW
{
    "model": "gpt-3.5-turbo",
    "max_tokens" : 2000,
    "messages": [
        {
            "role": "system",
            "content": "You are a helpful assistant."
        },
        {
            "role": "user",
            "content": "<<lcQuestion>>"
        }
    ]
}
ENDTEXT

The actual code calling ChatGPT is put in goBackgroundprocess.Script.

So far so good. Simple code is running without using the script and the payload properties.

However, when adtivating this line
Code:
 loHTTP.Send(lcPayload)
I get an error.

I have compared the content of lcPayload with the one I use in another VFP program (calling ChatGPT in a prg) and both texts are identical. I verified this too by using and this indicated too that both texts / strings are identical.

So I'm puzzled now. Is it possible that
Code:
 Execscript(This.Script)
still doesn't understand / read the variable lcPayload? What else could be happening here?

Regards, Gerrit
 
UPDATE:

The problem must be in the lcPayload variable when trying loHTTP.Send().

I tried using TEXT TO goBackgroundprocess.Script TEXTMERGE NOSHOW with loHTTP.Send(<<lcPayload>>).

This will add the variable's content to goBackgroundprocess.Script, but when trying to run the code in goBackgroundprocess.Script it fails once again on the loHTTP.Send line.
I copied this code to a prg and get an error in the same line.

I have to quit for now, but will be back tomorrow!

Regards, Gerrit





 
Using textmerge with loHTTP.Send(<<lcPayload>>) yoiu put the value of the string inside the << >> brackets. Well, that's causing the problem.

Especially when but not only when it's multiline.

You need to make lcPayload available to the script, you store it into a property of your instance of the COM server class. Well, a script that's executed by EXECSCRIPT has no concept of its origin, THIS has no meaning, so the script can't access a payload proeprty.

Directly embdding the payload with this textmerge misses at least the quotes and besides that has the same diffficulties with texts spanning multiple lines as you already encountered using quotes within a question "content": "<<lcQuestion>>".

Well, let's stop at that and sit back to see what you actually need.

You don't need my COM server for sending out requests in a background process, as you can simply use the asynchronous mode of the MSXML2.ServerXMLHTTP or other http request classes. That feature of not awaiting the ChatGPT result is already in that without any further wrapper. So why don't you change the script to async sending? Look at the third parameter of the open method:


MS said:
bAsync(optional)
Boolean. Indicator as to whether the call is asynchronous. The default is False (the call does not return immediately).

So instead of [tt]loHTTP.Open("POST", lcURL, [highlight #FCE94F].F.[/highlight])[/tt] doing [tt]loHTTP.Open("POST", lcURL, [highlight #FCE94F].T.[/highlight])[/tt] makes it an asnchronous request. I already mentioned it. It has other consequences, the code following Send() has to differ, because in case send() returns immediately the response is not there, i.e. loHttp.readyState is not 4.

Actually at that stage as you want parallel execution you'll also not put anything after the send that processes the ChatGPT response, you want to do that later, separately. So receiving the answer has to be done elsewhere, you need something that can check for the redyStte of loHTTP and then take in the a response, like a timer that checks every 500ms - whatever you think is seldom enough to not look so often, you do mostly loking and then could also use the normal synchronous mode that waits for the response.

The most important thing is you need to ensure that loHTTP is kept in memory, it can't be a local variable that is released before the response comes back, as that would mean the response is discarded. So this kind of parallelism needs some other thinking and architecture of your code, but you have it already available within MSXML2.ServerXMLHTTP, so use that.

OpenAI even ofgfers usage of a streaqming mode, where you get several responses, a stream of responses, that reflect what you experience when asking ChatGPT online interactively, the answer builds up and you can see what ChatGPT builds up. That requires even more, I haven't tried to receive a stream with MSXML2.ServerXMLHTTP or similar httprequest classes/functions.

Anyway, you actually use one of the few COM server classes of the Windows OS that allow asynchronous usage in themselves without the need of VFP to become multithreaded. It's also quite easy for MSXML2.ServerXMLHTTP to be asynchronous, as what it mainly does is forward your request to a remote server, then itself also just waits for the response. And does so in it's own process. So it can easily let you continue, unless you destroy it by releasing loHTTP.

Chriss
 
Hi Chris,

In the end I was able to get the background application running with my script calling ChatGPT. In spite of the background.exe my calling still halts and freezes temporary while waiting for a webservice response so it's unthinkable to use this in real life.

I tried the synchonous request as you suggested, but I cannot find a way to halt the process waiting for a result. Checking for the readyState (and wait while the process is still running) after send() is not enough. I tried to find help with Google, but couln't find any sample code to study.

So I'm still stuck, finding a way to call a webservie without my application becoming inresponsive. I've been testing with 100Mbit fiber, but also with ADSL with only 1Mbit download and 200 Kbit upload. A freezing application is out of the question.

Good weekend!

Regards, Gerrit
 
It would help to see your coding.

Gerrit said:
I tried the synchonous request as you suggested
Asynchronous, not synchronous. You don't get what it means to be synchronous vs. asynchronous.
You set bAsync to .T. to make it asynchronous, which means you don't synchronize and wait.

Gerrit said:
I cannot find a way to halt the process waiting for a result.

Well, the idea is nt to wait, if you'd do, you could also stick to the synchronous way.

The two tasks I tallked about is
1. Keeping loHttp alive while the response has not returned.
Well, what can be done? You can store loHTTP into a collection that is a property of the form, for excample.

2. Checking loHTTP.readyState from time to time.
The second thing is necessary as you unfortunately can't use a feature of MSXML2.ServerXMLHTTP the onreadystatechange callback, VFP can't provide pointers to callbackfunctions, vfp2c32.fll could help with that, or what I suggested to use: A timer.

I think it's already asked too much. I'll see when I could help out with that, an easy thing you can do is:

Code:
loHTTP.Open("POST", lcURL, .T.)
... other preparations before send
loHTTP.Send(lcPayload)
Do While loHTTP.readyState<4
   Doevents
Enddo

Because what does responsibveness mean? The simple events like mouse movwes and keyboard input still work. And that's provided by repeatedly doing DOEVENTS while waiting. It could be better than that, but you may give that a try.

Chriss
 
Gerrit, as I told you in Foxite, you can process the Web Service call asynchronously and process the response as it arrives, either by checking with a timer or implementing the WinHTTPRequest events.

I pointed you to an example of the latter approach, the overHere class library, but to facilitate the comprehension of what is required, you may refer to the following simplified demo.

The demo form views random images downloaded every 5 seconds and displays them without interrupting the regular reading of events.

Since the call to the web resource is asynchronous, it returns immediately. Only when the downloading is complete is a callback method in the form called and the downloaded image displayed. In the meantime, the form continues to be responsive.

You can adapt the demo to try with more greedy web services and verify if the responsiveness persists.

Code:
LOCAL Demo AS ViewRandomImage

m.Demo = CREATEOBJECT("ViewRandomImage")
m.Demo.Show()
READ EVENTS

DEFINE CLASS ViewRandomImage AS Form

	ADD OBJECT editMe AS EditBox WITH Top = 4, Left = 4, Width = 300, Height = 200, ;
		Value = "Edit me while images are being downloaded..."
	ADD OBJECT clicker AS CommandButton WITH Top = 220, Left = 4, Caption = "Click me", Autosize = .T.
	ADD OBJECT clickCounter AS Label WITH Top = 254, Left = 4, Caption = "Clicked 0 times", Autosize = .T.

	ADD OBJECT Viewer AS Image WITH Top = 4, Left = 308, Width = 1000, Height = 500
	ADD OBJECT Reloader AS Timer WITH Interval = 5000

	Downloader = .NULL.

	Clicks = 0

	Width = 1350
	Height = 520

	PROCEDURE Init ()

		This.Downloader = CREATEOBJECT("DownloadRandomImage", This)
		WITH This.editMe AS Editbox
			.SelStart = 0
			.SelLength = LEN(.Value)
			.SetFocus()
		ENDWITH

	ENDPROC

	PROCEDURE Destroy ()

		CLEAR EVENTS

	ENDPROC

	PROCEDURE clicker.Click ()

		Thisform.Clicks = Thisform.Clicks + 1
		Thisform.clickCounter.Caption = "Clicked " + LTRIM(STR(Thisform.Clicks)) + " time" + IIF(Thisform.Clicks != 1, "s", "")

	PROCEDURE Reloader.Timer ()

		IF ! ISNULL(Thisform.Downloader)
			Thisform.Downloader.Download()
		ENDIF

	ENDPROC

	PROCEDURE Callback (StatusCode AS Integer, ImageBlob AS Blob)

		IF BETWEEN(m.StatusCode, 200, 299)
			This.Viewer.PictureVal = m.ImageBlob
		ENDIF

	ENDPROC 

ENDDEFINE

DEFINE CLASS DownloadRandomImage AS Custom

	IMPLEMENTS IWinHttpRequestEvents IN "WinHttp.WinHttpRequest.5.1"

	HTTPService = .NULL.
	CallerForm = .NULL.

	FUNCTION Init (Caller AS Form)

		This.CallerForm = m.Caller

	ENDFUNC

	FUNCTION Destroy ()

		This.CallerForm = .NULL.
		This.HTTPService = .NULL.

	ENDFUNC

	FUNCTION Download ()

		IF ! ISNULL(This.HTTPService)
			TRY
				This.HTTPService.Abort()
			CATCH
			ENDTRY
			This.HTTPService = .NULL.
		ENDIF
		This.HTTPService = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
		This.HTTPService.setTimeouts(0, 30000, 60000, 60000)
		EVENTHANDLER(This.HTTPService, This)

		This.HTTPService.Open("Get", "[URL unfurl="true"]https://picsum.photos/1000/500",[/URL] .T.)
		This.HTTPService.Send()

	ENDFUNC

	PROCEDURE IWinHttpRequestEvents_OnError (ErrorNumber, ErrorDescription)
	ENDPROC

	PROCEDURE IWinHttpRequestEvents_OnResponseDataAvailable (Data)
	ENDPROC

	PROCEDURE IWinHttpRequestEvents_OnResponseFinished

		TRY
			This.CallerForm.CallBack(This.HTTPService.status, This.HTTPService.responseBody)
		CATCH
		ENDTRY

	ENDPROC

	PROCEDURE IWinHttpRequestEvents_OnResponseStart (Status, ContentType)
	ENDPROC

ENDDEFINE
 
Hello atlopes,

didn't thought of implementing an interface of a httprequest class, well done.

atlopes said:
...either by checking with a timer...
Gerrit doesn't seem to know what to implement in the timer or even creating a timer class.

Gerrit, I really wonder why you then don't ask back.

Chriss
 
First of all my apologies for mixing up sync and async. This was just a typo, my bad. I did try with the right code.

In this webservice (as suggested by Chris) I use MSXML2.ServerXMLHTTP. Antonio, you’re using WinHttp.WinHttpRequest.5.1.

Do both objects have the same properties? In other words, can I replace the object name in CREATEOBJECT or are more changes needed. I’ve been running in circles now for a week trying many different options, so it’s time to get something that’s working.

One other comment about downloading a picture while waiting (at least that’s what I think it is). With certain very slow internet connections that’s not a good idea. On one particular location we can only get a download of around 1 Mbit max.. A simple message would be good enough for that purpose.

I will try some thing as suggested later this weekend or early next week.

Regards, Gerrit
 
First of all WinHttp.WinHttpRequest.5.1. and MSXML2.ServerXMLHTTP. and also a few further classes that all go under the roof of httprequest classes all offer the same methods and properties, mainly the opnen(), send(), readystate and requestbody, also the setrequestheader etc. There are slight differences, but you're able to switch from MSXML2.ServerXMLHTTP to WinHttp.WinHttpRequest.5.1, that's not a problem.

Gerrit said:
One other comment about downloading a picture while waiting (at least that’s what I think it is)...

Gerrit, you're not seeing the wood for the trees, atlopes examples shows how to let the arrival of a response trigger a callback which then reacts to the response. And from send to that point you don't need any code waiting for the response, the callback mechanism allows that you specify what should be called when the response arrives and then send the request and finish at that moment. The application is responsive and whenever the response arrives that interrupts the application again, shortly, to do the callback.

His sample code just uses an image download, but what you do there is up to you. If you can't see how this solves your issue of keeping an application responsive, then I'm also out of ideas on how to guide you. I assume you just copy&pasted the code to see what it does. Well, you don't learn from that. Read the code and adapt it for your use case.

Chriss
 
Gerrit,

MSXML2.ServerXMLHTTP does not expose its events in the same manner that WinHttp.WinHttpRequest does. But, on the other hand, you're trying to fetch non-XML data, so you may safely disregard the use of MSXML2.ServerXMLHTTP (but if you needed to fetch XML data, the overHere library could also be used as an example of interaction between WinHTTP and MSXML2).

As Chriss pointed out, you must understand the approach. Then, it is just a matter of implementing it for the web service and data you're trying to consume and process. Forget image downloading. This is about accessing a web service without freezing your application.
 
Gerrit,

let me guide your attention to this part of the code:

Code:
	 This.HTTPService.Send()
ENDFUNC

There's an ENDFUNC after the Send(). Can you see what that means? There is no code waiting for the response there or processing it, that's done separately.

Before I get to the how to, just realize what it means when you want your application to stay responsive. Ask yourself just one simple question, at what stage is a VFP application reysponsive, responsive to any interaction with it by mouse or keyboard? No idea? It's wehn it's at the READ EVENTS line, that's where the callstack gets to when any method or function you call ends. If you don't have a READ EVENTS, then you have a modal main form, that's also working the same way, no code executes, all functions/methods etc are done. That's when an application can react to events again.

The second way to process events is when you use the command DOEVENTS. Look a few posts back, I mentioned it.

And now the bis question is, how does the response to the request come back and where and how is it processed. I'll tell that in the next post, you could just read the code and find out. It pays off with a good feeeling to get the idea yourself.


Chriss
 
Hi Chris,

I know, I’m reading a lot but had no time yet to work with the samples. Give me some time and I will get there.

Regards Gerrity
 
That's not necessary, Manni.

Just copy&paste atlopes code into a PRG and run it.
You can use the button and see the click count go up, you can edit the text in the editbox, also while a new picture is loaded by an asnchronous HTTP request.

And this request could instead go to Open AI, that's not the probem.

Chriss
 
Gerrit,

as continuation of the explanation of atlopes code, look at this code portion, now:

Code:
	PROCEDURE IWinHttpRequestEvents_OnResponseFinished

		TRY
			This.CallerForm.CallBack(This.HTTPService.status, This.HTTPService.responseBody)
		CATCH
		ENDTRY
	ENDPROC
The most important part is that this is the implementation of an event of the WinHttp.WinHttpRequest.5.1 COM class, which the MSXML2.ServerXMLHTTP doesn't have. An event that happens when the response from the request comes back. The callback. And therefore the copde of this is the call of the callback defined in the architecture as a method of the callerform.

You can vary this a lot, it's not even important to put this into TRY..CATCH., but it helps to suppress problems.

The whole thing only works in conjunction with other details, one of which is that this class has the clause IMPLEMENTS IWinHttpRequestEvents IN "WinHttp.WinHttpRequest.5.1" and that this class is connected to an actual "WinHttp.WinHttpRequest.5.1" request object with the EVENTHANDLER() function. Atlopes integrates or encapsulates all this in the same class.

What is to understand about this is that a class with an IMPLEMENTS clause specifies a promise to implement all events of an interface defined for the COM class. This comes from the typelib of the COM object and defines the methods that need to be implemented and their names and parameterization, i.e. it means you inherit something designed from the COM class. The astonishing thing, technologically is, that this COM server obviously isn't programmed in VFP, so you inherit an interface definition from another programming language and can program the events in FoxPro code.

Well, it's not necessary to share the enthusiasm for object oriented programming, but this is an elegant way not only because it overcomes language barriers.

The most intersting aspect of this is that unlike waiting by sending the request synchronous or, even if you'd switch to sending asynch but still use a timer or a while loop to wait for the responsebody to become available, this will automatically happen and call back to your class implementing tzhe event OnResponseFinished.

I can understand why it's not tht easy to see for yourself, but at least the name should ring a bell and interest you because OnResponseFinished describes what you await, you want to present the response of ChqatGPT to the user when it's done.

And the way this all works together also automatically means your application is responsive in the meantime betwen the Send sending the request and the OnResponseFinished event coming back with the response.

Now ChatGPT might need a minute or two, it won't affect the users ability to continue using your application, writing text or whatever.

I'm telling all this not because you asked directly, but you asked indirectly by rejecting atlopes sample as unusable. You obviously didn't even get that this isn't bound to only downloafd pictures, it isn't even bound to doing an automatic Download() call in a timer. Atlopes just chose this for having a repeat switch of two states of the application: Waiting for the next picture and not waiting. And demonstrating that in both states the application can handle and be responsive to userinput or clicks. And this would even be the case, if the request takes ChatGPT longer, unless the user closes the application before the response comes back.

You can stil reuse code for the request, because also WinHttp.WinHttpRequest.5.1 has the necessary SetRequestHeader to set the OpenAI API key and the send can send a body/payload. You asked whether this is the same and I already answered that with yes, previously, by the way. MSXML2.ServerXMLHTTP does not expose this event, not even for this rather complicated way of implementing it.

That means you would not have the chance to get the response just when it arrives, so this mechanism is a bit more complex than checking readyState to become 4, but you can react to the response right when it arrives, without needing to actively check readyState or wait for it and making the application unresopnseful during that time.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top