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!

How to force a value to be physically read from a table 2

Status
Not open for further replies.

Mike Lewis

Programmer
Jan 10, 2003
17,516
Scotland
Let me see if I can explain this problem.

I have two programs running at the same time on the same computer. Call them ProgA and ProgB. They are both EXEs.

Once in a while, ProgA writes a value to a table. The table is a DBF file with a single record. ProgA simply updates one field in this record.

At frequent intervals (approx. once per 250 ms), ProgB reads the value. If it has been updated, it takes some action.

The problem is that ProgB isn't seeing the latest value of the field. It seems to be always picking up the original value, as it was when the table was first opened, rather than the updated value.

To illustrate the code (hightly simplified):

Code:
* ProgA - Does this very occasionally
UPDATE TheTable SET TheField = .T.

* ProgB - At start of process
USE TheTable IN 0
SELECT TheTable
GO TOP  && not really necessary as there's only one rec.

* ProgB - every 250 ms
IF TheTable.TheField
 * Do something
ENDIF

The problem is that, when that last bit of code fires, ProgB is always seeing the original value in TheTable.TheField. It's not picking up the updated value. Apparently, VFP is not physically re-reading the record; it has no way of knowing that it's changed.

Can anyone suggest a way of forcing ProgB to physically re-read the record so that it can see the latest value?

In fact, I can do this by closing and re-opening the table immediately before ProgB looks at the field. That solves the problem. Unforunately, it also introduces a lot of overhead. The process takes 40 percent longer as a result.

I'm reasonably sure that ProgA is physically writing the value immediately after the INSERT. If I do a FLUSH or close the table after the INSERT, there's no change in the behaviour of ProgB.

Hope this makes sense.

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 
i use the same scenario for a sync program and an POS program.

the POS updates a field POSSync with .f.

the Sync Utility reads that records.. and its been working fine..


After I do this:

Select MyTable
replace POSSync with .f.
Skip 0 && this seems to help commit the changes..

however, my intervals are every 5 seconds.... i don't know if that make a difference.

Ali Koumaiha
TeknoSoft Inc.
Michigan
 
As you say ProgB can read the new value after closing and reopening the DBF this at least shows ProgA is successfully commiting it's change.

Try Curval() in ProgB, which by definition reads from disc, no matter what is cached. You may neverthelesse do Sys(1104) to purge the memory cache, it wouldn't hurt doing so in both programs.

Bye, Olaf.
 
Other suggestions: In one case I use a text file to exchange infos between two processes, that is not affected by caching and buffering techniques and should work fine for such a boolean flag.

And of course there are other possible interprocess communications, which even would not need a timer. While i's outdated, think about DDE. You don't need a timer to poll for a signal, you get called via a DDE request.

I used this a while back and it's still working on Vista.

ProgA, instead of checking the bool flag, does act as a callable DDE server, which you can setup at program start:
Code:
#Define DDE_SERVER_NAME "ProgA"
Public goDDE
goDDE = CreateObject("DDEService")

* or perhaps add it to an application object: goApp.AddObject("oDDE","DDEService")

Define Class DDEService As Custom
   Procedure Init()
      * create DDE Service, support the Action "EXECUTE"
      Ddesetservice(DDE_SERVER_NAME, 'DEFINE')
      Ddesetservice(DDE_SERVER_NAME, 'ADVISE', .F.)
      Ddesetservice(DDE_SERVER_NAME, 'EXECUTE', .T.)
      Ddesetservice(DDE_SERVER_NAME, 'POKE', .F.)
      Ddesetservice(DDE_SERVER_NAME, 'REQUEST', .F.)

      * offer Topics:
      DdeSetTopic(DDE_SERVER_NAME, "Callback", "RunMethod")
      * add as many DdeSetTopic as Methods you want executable
      * if a client initialises a topic "Callback",
      * then "RunMethod" is the method run, when a DDEEXECUTE is issued.
   Endproc

   Procedure Destroy()
      * release the default service and its avaialable interfaces
      =Ddesetservice(DDE_SERVER_NAME, 'RELEASE')
   Endproc
Enddefine

Procedure RunMethod()
   Lparameters tnChannel, tcAction, tcItem, tuData, tcFormat, tnAdvise

   * tuData will be, what you pass via DDEEXECUTE() in a client prog
   IF tcAction = "EXECUTE" && DDE action supported (see Init)
      * Do something
   ENDIF
Endproc

ProgB, instead of setting the bool flag, calls the DDE Server:
Code:
lnDDEChannel= DDEINITIATE("ProgA", "Callback")
If lnDDEChannel#-1
   DDEEXECUTE(lnDDEChannel, "Parametervalue")
   DDETERMINATE(lnDDEChannel)
Endif

You got to add some error handling as you like.

Bye, Olaf.
 
I think I got ProgA and ProgB the other way around in my DDE example, I think you get it anyway. The prog being triggged to do something is the DDE server, the prog triggering to do something is the DDE client.

Bye, Olaf.
 
Thank you all for your excellent suggestions. I've just spent a happy morning experimenting with them.

Olaf: I decided to go with your idea of using CURVAL(). It solved the problem, and doesn't seem to add any noticecable overhead. Thanks.

Thanks also for reminding me about DDE. I might have considered it if I had thought of it early enough, but CURVAL() was easier to retrofit. (Then again, I think DDE might also involve a lot of overhead - not sure about that).

Dave: SET REFRESH seems like the obvious answer. I don't know why I didn't think of it myself. But for some reason, it didn't work. I thought SET REFRESH TO 1,-1 should solve the problem, but it made no difference.

Ali: Yes, I can see that SKIP 0 might help commit the updates, but that wasn't my problem. I was confident that ProgA was correctly writing the value, without any delay. The problem was that ProgB was reading the cached value, not the actual value on disk.

I also tried adding SKIP and then SKIP -1 to ProgB, in the hope that would force it to re-read the record, but it didn't.

On reflection, I can see now that I asked the wrong question. Instead of asking how to force values to be read from the table, I should have asked: what is the best way for two asyncrhonous processes to send signals to each other?

A simpler solution might have been not to have a table at all, but for ProgB to test for the presence of a given file in a given directory. That would have been simpler, but I won't do it this time, as Olaf's CURVAL() solution was easier to add to the existing code.

So thanks again. I hope this thread will be useful to others.

Mike




__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 

Mike,

Though you have found a solution - maybe could also try the VFP FLUSH / FLUSH FORCE command to ensure that the table contents were written to disk.

 
Thanks Mike,

and glad it helped. That you need Curval() is a sign ProgB does open the table buffered, although Curval() also works in unbuffered mode and with MUILTILOCKS OFF. Perhaps take a look that you didn't set a general buffering mode to all workareas by CURSORSETPROP("Buffering",Buffermode,0), which set's it for workarea number 0, the empty workarea, which does set it for all dbf you open from that point on.

The only other reason that would cause a read of an old value than buffering is caching, and that would point to a caching bug or issue. Maybe not the fault of the VFP runtime, but deeper buried in the file system and network protocols, eg a SMB2 issue again, perhaps.

Bye, Olaf.
 
Oh, and jsut one more thing: besides Curval(), also any SELECT-SQL will always read from disc (but aqlso from cached data), unless you specify WITH (Buffering=.T.), which makes SQL read from the table buffer. Adressing the table field via alias.fieldname is always reading the buffered value, if there is one, though.

But a field/record is only buffered, if you change it, not if you only read it, which is the thing that keeps me scratching my head about the bahaviour you see. It's not a big issue, otherwise it would cause much more trouble.

Bye, Olaf.
 
Olaf,

I must say I'm a bit puzzled. You wrote:

That you need Curval() is a sign ProgB does open the table buffered ....

In fact, the table wasn't buffered (nor did I set buffering for all work areas). ProgB doesn't write anything to DBFs, so I had no reason to buffer anything.

You also wrote:

, although Curval() also works in unbuffered mode and with MUILTILOCKS OFF.

That's not what I saw. The first time I tried it, it gave an error message to say that CURVAL() required the table to be buffered. Once I set MULTILOCKS on and set the table's buffer mode, it worked fine.

I take your other point, about SQL SELECT picking up the data from the disk rather than the buffer. I didn't try it because I had the feeling that CURVAL() would be faster.

Anyway, I won't take this any further for now. I've released the updated application, and am moving on to other problems. But I'll keep your good advice in mind.

If I'm ever in this situation again, I'll consider not using a DBF at all, but perhaps test for the presence of a given file in a given directory, or something similar.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips, training, consultancy
 
Hi Mike,

you're right, Curval() requires row or tablebufferingmode, and I was making a wrong observation in regard of MULTILOCKS ON/OFF there.

Bye, Olaf.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top