第十二章 并发共享访问编程及其它.docx
《第十二章 并发共享访问编程及其它.docx》由会员分享,可在线阅读,更多相关《第十二章 并发共享访问编程及其它.docx(23页珍藏版)》请在冰豆网上搜索。
![第十二章 并发共享访问编程及其它.docx](https://file1.bdocx.com/fileroot1/2023-4/21/98024058-5ab4-46af-8f59-8829bdf50f32/98024058-5ab4-46af-8f59-8829bdf50f321.gif)
第十二章并发共享访问编程及其它
第十二章并发共享访问编程及其它
12.1多用户环境及多用户操作函数、命令1
12.2使用数据工作期5
12.3用缓冲(Buffering)访问共享数据7
12.4用事务处理(Transaction)管理更新数据12
12.5Internet与VFP的ActiveDocument16
如果建立的应用程序在网络环境中的多台计算机上运行,或者一个表单的多个实例对相同数据进行访问,这就要进行共享访问程序设计。
共享访问不仅能为用户使用数据和数据共享提供更有效的方法,而且在必要时可对访问进行限制。
VFP具有对数据的共享或独占访问;锁定选项;数据工作期;记录缓冲和表缓冲以及事务处理等功能。
尽管这些功能主要用在共享环境里,但在单用户环境下也可以使用。
12.1多用户环境及多用户操作函数、命令
FoxPro2.5以上的FoxPro和VFP都是多用户版本,没有单用户版本了。
在一个网络环境(例如Novell网文件服务器)中,一个工作站(Workstation)上的用户试图访问一个正被另外的工作站用户访问的记录或文件就会产生碰撞(Collision)。
当这种情况出现时,如果多于一个用户打算修改这个记录,则会导致损坏数据的结果。
于是便出现了象APPEND、APPENDFROM、ALTERTABLE、INDEX、INSERT、JOIN和UPDATE、UPDATE-SQL等能够自动加锁(隐含加锁)整个表,象APPENDBLANK、APPENFROMARRAY、INSERT-SQL等能自动加锁表头,象APPENDMEMO、DELETE、DELETE-SQL、GATHER、BROWSE、CHANGE、EDIT、MODIFYMEMO、READ、RECALL、REPLACE、SHOWGETS等能自动加锁记录的命令(CURSORSETPROP()取决于缓冲进行自动加锁),防止其它工作站破坏用户正访问的数据。
但如果不及时解锁,又可能出现死锁(DeadlyEmbrace)的情况,即:
当一个用户锁定了一个记录并希望访问一个正被第二个用户加锁了的记录,或反之,第二个用户正试图访问第一个用户锁定了的记录,等等。
死锁的出现是很少的,但随着一个记录加锁的时间的增加,它的可能性也在增加。
VFP提供了如下一些人工明显文件加锁、人工明显记录加锁和解锁以及捕捉错误的函数和命令,可有效地解决上述问题(要求只读访问一个表的命令,不用文件加锁和记录加锁)。
1.表文件加锁的函数FLOCK()
格式:
FLOCK([<工作区号或别名>])
说明:
该函数试图将当前或指定工作区的表文件锁定(锁定全部记录)。
如果加锁成功,返回.T.(真),这样可读写该表,其它用户只能读不能写。
如果加锁不成功,则返回.F.(假),表示这个表或表中的一个记录早已被其它用户锁定了。
函数返回类型逻辑型。
用户可以根据情况用UNLOCK、USE、CLEARALL或者CLOSEDATABASE等命令来关闭表。
在缺省情况下,FLOCK()只对一个表进行一次锁定尝试。
用SETREPROCESS,当第一次尝试失败时自动地再次锁定这个表。
SETREPROCES确定尝试的次数和再次尝试的间隔时间(详见VFP软件的Help)。
用户还可以完全独占地加锁表,防止其他用户访问它(即,将其他用户的只读访问权也剥夺掉),用SETEXCLUSIVEON和USE<表>EXCLUSIVE命令便可独占表。
在前面章节,介绍表索引时,已这样用过独占设置命令。
2.表记录加锁的函数RLOCK()
格式:
RLOCK([<工作区号或别名>]|[,[<字符表达式列表>][,<工作区号或别名>]])
说明:
VFP的RLOCK()函数可锁定多个记录。
其中[<字符表达式>]任选项指定一个或者多个记录号,并用逗号将它们分开,这样RLOCK()函数将试着给这几个记录加锁。
例如,要给表中的前5个记录加锁,<字符表达式>就必须含有1,2,3,4,5。
[<字符表达式>]不选,只给当前记录加锁。
用户要给多个记录加锁必须将SETMULTILOCKS设置为ON,并且要给出记录所在工作区号或表的别名。
用户还可将记录指针移到相应记录,然后给出RLOCK()函数,重复以上过程给其它记录加锁,同样可达到给多个记录加锁的目的。
在VFP中,可将记录号设置为0,这样用户可给表头加锁,锁定库头时间一定要短,因为一旦锁定了库头,其他用户便不能在表中添加或删除记录了,但可修改字段内的数据。
表锁定命令锁定整个表,比表头锁定命令更严厉。
当前或指定工作区表中由<字符表达式列表>所指定的记录都加上锁,RLOCK()将返回.T.(真),如果<表达式列表>中的一个或者多个记录不能被锁定,RLOCK将返回.F.(假),并且不给任何一个记录加锁。
在上述两种情况下,仍然保留以前已经存在的记录锁定。
函数返回类型逻辑型。
在网络中,只有加锁者本人才能对加锁记录进行读写操作,这些记录对于其他用户来说是只读的。
记录锁定只能由加锁者本人打开。
用户可以通过给出UNLOCK命令,关闭表或者退出VFP系统都能够给记录解锁。
命令UNLOCK[RECORD<记录号数值表达式>][IN<工作区号或别名>][ALL]可以用来解开当前工作区、指定工作区的指定记录或所有工作区的记录锁定。
一般RLOCK()为记录进行一次加锁尝试,多次加锁尝试的处理与FLOCK()的多次尝试相同。
LOCK()与RLOCK()完全等同。
3.检测ONERROR语句捕捉的出错号ERROR()的函数
格式:
ERROR()
说明:
ERROR()返回最近的一个错误号。
在ONERROR命令被激活的情况下,ERROR()返回一个非0值。
函数返回类型数值型。
当程序执行时遇到了错误(如隐含加锁失败等),可用ONERROR程序中的ERROR()函数查明错误的类型,用MESSAGE()函数可返回相应的错误信息。
ERROR()返回值可用RETURN或RETRY来复位,复位为0。
4.检测ONERROR语句捕捉的出错号对应的出错信息的函数MESSAGE()
格式:
MESSAGE([1])
说明:
此函数功能是:
以字符串的形式返回当前的错误信息。
任选项[1],表示可返回引起错误的源程序命令行内容。
如果此程序代码不能用,则MESSAGE
(1)将返回以下几种形式之一。
(1)如果此行是宏替换的,返回整个程序行。
(2)如果此行包含的命令没有任何附加的子句时,返回此命令。
(3)如果此行包含有命令和附加的子句时,将返回此命令和后面三个小点(…)。
本函数返回类型为字符型。
和ERROR()函数不一样,MESSAGE()不能由RETURN或者RETRY重新设置。
5.关于多用户的系统的函数SYS()
格式:
SYS(<数值表达式>)
说明:
详见4.7节。
另外,还有几个重要的与多用户函数相关的多用户命令:
如SETPRINTERTO可设置网络打印等;RETRY命令可将控制返回给调用的程序并且在调用不是重新执行最后一行(RETRY)外,RETRY命令类似于RETURN(返子程序)命令。
当一组命令必须重复直到一个确定的条件为真时,RETRY命令特别有用。
比如RETRY在错误处理程序中是很用的,在给记录或者文件加锁的时候也是这样,RETRY频繁地执行同一个命令,直到成功地给记录或者文件加上锁为止。
用户可以使用SETREPROCESS来控制记录或者文件加锁函数的执行。
在大多数网络环境中,使用SETREPROCESS更为可取。
例12.1:
使用APPENDBLANK隐含加锁文件头和在与其他用户来“碰撞”时进行捕捉错误的处理。
SETEXCLUSIVEOFF&&设置为共享环境
ONERRORDOfix
USEVideo
APPENDBLANK
ONERROR
***下面的Fix.PRG是处理上面错误的子程序
IFERROR()=108&&108错误是文件被另外的用户在用
@23,0SAY"请等待去添加一个记录"
RETRY
ELSE
@23,0SAY"请去咨询你的系统管理员,关于这个错误是什么原因"+MESSAGE()
WAIT
ENDIF
例12.2:
共享表情况下的文件加锁。
SETEXCLUSIVEOFF
SETREPROCESSTO0
USEVideo
IFFLOCK()
REPLACEALLCost_rentWITHCoxt_rent+1.00
UNLOCK
ELSE
@22,0SAY"Fileisusebyanother"。
ENDIF
在上面例子中,表Video为共享访问而打开。
FLOCK()函数与IF…ELSE…ENDIF结构结合去确定锁定状态。
如果文件由于没有另外的用户使用而加锁成功,则执行REPLACEALL命令,并且租用一个录像(Video)带的价钱上涨1.00美元。
完成REPLACEALL操作后,执行UNLOCK命令去解除文件锁定。
如果文件由于另外的用户已使用而加锁失败的话,则出现一个“文件被另外的人使用”的屏幕信息。
例12.3:
记录加锁。
SETEXCLUSIVEOFF
USEVideo
LOCATEFORCost_rint=25.00
DOWHILE.NOT.LOCK().and.TIME<1000
TIME=TIME+1
ENDDO
IFTIME<1000
REPLACECost_rentWITHCost_rent+5.00
ELSE
@23,OSAY"记录不能加锁,稍后再试。
"
ENDIF
上面的例子中,LOCATE命令找到Video库中第一个Cost_rint=25.00元的记录,并用Lock()函数与DOWHILE…ENDDO循环结构结合去尝试锁定该记录。
如果加锁不成功,则计次数变量TIME加1,再循环去加锁,直到LOCK()为真(.T.)即.NOT.LOCK()为假(.F.)时跳出循环(或者加了1000次也不成功,即TIME>1000,也跳出循环)。
若TIME<1000时加锁成功,则执行将该记录租金字段加5.00元的REPLACE语句。
否则在屏幕上显示“记录不能加锁,稍后再试”的信息。
12.2使用数据工作期
为确保共享环境中的每个用户都具有安全、正确的环境,确保表单的多个实例能独立操作,VFP提供了数据工作期。
数据工作期是对当前动态工作环境的描述。
可以将数据工作期看成是一个小型的数据环境,这个环境是在一台机器上运行的一个开放的VFP工作期。
每个数据工作期包括:
(1)表单的数据环境中各项的备份。
(2)表示打开的表、索引和关系的临时表。
请考虑当在多用户应用程序的各自工作站同时打开相同的表单时,会发生什么,这样可以很容易理解数据工作期的概念。
这种情况下,每个工作站运行一个独立的VFP工作期,因此有自己的工作区设置以及自己表示打开的表、索引和关系的临时表。
但是,如果你在一个机器上,同一个VFP工作期,打开单个项目中的同一个表单的多个实例,则这些表单共享默认的数据工作期,它代表了单个的动态工作环境。
在同一个VFP工作期中打开的表单的每一个实例使用相同的工作区设置,并且在一个表单实例中移动工作区中的记录指针会自动影响相同表单的其他实例。
12.2.1使用私有数据工作期
如果想更多地控制表单的多个实例,可以使用私有数据工作期。
当表单使用私有数据工作期时,VFP为应用程序创建的表单、表单集或工具栏中的每个实例创建一个新的数据工作期。
每个私有数据工作期包括:
(1)表单的数据环境中每个表、索引和关系的独立备份。
(2)数目不限的工作区。
(3)独立于表单基表的每个备份表的记录指针。
若要使用私有数据工作期:
①在“表单设计器”中,将表单的DataSession属性设置为“2_私有数据工作期”。
或者,
②在代码中将DataSession属性设置为2。
例如,可键入:
frmFormName.DataSession=2
当表单使用的是私有数据工作期时,在单个的机器上,单个的VFP工作期中打开表单的每个实例使用自己的数据环境。
使用私有数据工作期类似于在不同的工作站上同时运行同一个表单。
12.2.2识别数据工作期
每个私有数据工作期是单独识别的。
可以在“数据工作期”窗口中查看每个数据工作期的内容。
也可以通过在Load事件代码中的命令改变数据工作期的说明。
使用DataSessionID的运行时属性,可以查看每个数据工作期限的识别号。
下面的示例显示名为frmMyForm的表单的DataSessionID属性。
DOFORMfrmMyForm
?
frmMyForm.DataSessionID
DataSessionID属性只是用来识别特殊的数据工作期。
不要更改一个表单实例的DataSessionID属性,当更改DataSessionID属性时,与数据绑定的控件会丢失它们的数据源。
12.2.3使用多个表单实例更新数据
私有数据工作期生成各自独立的工作区,工作区包含了表单的表、索引和关系的独立备份,表单的每个备份引用了相同的基表和索引文件。
当用户从表单的一个实例中更新记录时,更新该表单引用的基表。
当定位到更改的记录时,就可以看到表单另一个实例所作的更改。
如果在一个私有数据工作期对记录或表进行了锁定,其他私有数据工作期就不能再进行锁定。
例如,数据工作期1的用户锁定了一个记录,数据工作期2的用户就不能锁定这个记录了。
如果数据工作期1的用户以独占方式打开了一个表,数据工作期2的用户就不能打开这个表了。
通过遵守其他数据工作期所作的锁定,VFP可以保护基表更新的完整性。
12.3用缓冲(Buffering)访问共享数据
如果希望在更新时保护数据,可以使用缓冲技术。
在多用户环境下,VFP的记录缓冲和表缓冲技术可以保护对单个或多个记录所做的数据更新以及数据维护工作。
缓冲区可以自动测试、锁定以及解锁记录或表。
借助缓冲技术,可以很容易地检测并解决数据更新操作过程中所遇到的冲突:
当前记录被复制到一个由VFP进行管理的内存或磁盘区域,其他用户仍然可以同时访问原来的记录,当离开记录或以编程方式更新该记录时,VFP将准备锁定该记录,确认其他用户没有做修改,然后写入编辑结果。
在试图更新数据时,还必须注意解决冲突,在冲突时防止编辑结果直接写入原来的表。
12.3.1缓冲编辑结果
在启动缓冲之前,应根据实际需要选择缓冲方法和锁定选项。
一旦启用缓冲,则在它被废止或关闭表之前一直保持有效。
1.选择缓冲方法
VFP有两种类型的缓冲:
记录缓冲和表缓冲。
(1)若一次只对一个记录进行访问、修改或写操作,一般应选择记录缓冲。
在多用户环境中,记录缓冲能够提供适当的有效检查机制,对其它用户所做的数据更新操作影响最少。
(2)若要对多个记录(一个表中处理多个记录或在一对多关系中处理子记录)的更新使用缓冲,应选择表缓冲。
(3)若要对已有数据提供最大程度的保护可使用VFP事务。
可以单独使用事务,但是如果将事务和记录缓冲或表缓冲命令一起使用,会获得更好的效果(事务详见下节)。
2.选择锁定方式
VFP有两种锁定方式提供缓冲:
保守式和开放式。
在多用户环境中,保守式缓冲能防止其他用户在对某一特定记录或表正进行修改时访问它。
保守式缓冲为单个记录的修改提供最安全的工作环境,但是会降低用户的操作速度。
开放式缓冲是更新记录的有效方法,因为锁定只在写记录时生效,这样在一个远程表上使用记录或表缓冲时,VFP将强制使用开放式锁定。
3.启用记录缓冲和表缓冲
可用CURSORSETPROP()函数且设置Buffering属性值,来决定缓冲和锁定的方法,并启用缓冲。
(1)启用保守式记录锁定
若要启用保守式记录锁定,可以设置:
=CURSORSETPROP("Bufferimg",2)
VFP在指针位置锁定记录。
如果锁定成功,VFP将记录放入缓冲区并允许编辑。
当移动记录指针或发出TABLEUPDATE()命令时,VFP将把缓冲记录写入原来的表。
(2)启用开放式记录锁定
(3)对多记录(即表)启用保守式锁定
若要对多个记录启用保守式锁定,可以设置:
=CURSORSETPROP("Buffering",4)
VFP在记录指针位置锁定。
如果锁定成功,VFP将把该记录放入缓冲区并允许编辑。
使用TABLEUPDATE()命令将把缓冲记录写入原来的表。
(4)对多个记录(即表)启用开放式锁定
若要对多个记录启用开放式锁定,可以设置:
=CURSORSETPROP("Buffering",5)
在发出TABLEUPDATE()命令之前,VFP把记录写入缓冲区,并允许编辑,然后VFP对缓冲区内的每一记录执行下列操作:
①对每一个已编辑记录锁定。
②一旦锁定成功,即对磁盘上的每一个记录的当前值与原来的缓冲区值进行比较。
③如果两值相同,将编辑结果写入原来的表。
④两值不同,给出错误信息。
在启用表缓冲之后,VFP只在TABLEUPDATE()命令之后更新。
对于表,Buffering的默认值为1(无缓冲);对视图,默认值为5。
如果使用缓冲访问远程数据,则Buffering属性可以是3(开放式行缓冲)或5(开放式表缓冲)。
注意:
对所有除1以外的缓冲方式,设置MULTILOKS为ON。
4.在表缓冲区中追加和删除记录
可以在启用表缓冲之后追加和删除记录。
追加的记录(用APPEND或APPENDBLANK命令)将添加到缓冲区末尾。
要访问缓冲区中所有记录,则使用RECNO()函数,RECNO()函数根据追加到表缓冲区中的记录情况返回序列负值。
比如,如果启用了表缓冲,编辑记录7、8、9,然后追加三个记录,则此时缓冲区将包含RECNO()的值为7、8、9、-1、-2和-3。
在使用表缓冲时,可以用带负RECNO()值的GO命令来访问指定的追加记录。
例12.4:
对前面的示例,可以键入:
GO7&&转到第一个缓冲记录
GO–3&&转到第六个缓冲记录(即第三个追加记录)
若要从表缓冲区中移去追加的记录:
可先使用带负值的GO命令将记录指针定位到要删除的记录;再用DELETE命令将该记录加上删除标记;最后用TABLEREVERT()函数将该记录从缓冲区中移去。
若要从表缓冲区中移去所有追加的记录,使用带(.T.)值的TABLEREVERT()函数。
TABLEREVERT()把追加的记录从表缓冲区中移去,而不将这些记录写入表。
TABLEUPDATE()命令将所有当前的记录写入一个表,包括已打上删除标记的记录。
12.3.2执行更新
在选择缓冲方法和锁定类型之后,可采用代码方式启用记录或表缓冲。
代码放置在表单或表单集的Init过程,然后把用于更新操作的代码放入适当控制项的相应方法中。
要把编辑结果写入原来的表,可以使用TABLEUPDATE()。
由于规则限制,造成对表的更新操作失败之后,要取消编辑结果,可以使用TABLEREVERT()。
TABLEREVERT()即使在没有明确启用表缓冲的情况下都有效。
例12.5:
下面的例子说明在启用保守式记录缓冲的情况下,如何更新记录
OPENDATABASEMYDATA&&在表单的Init代码,打开表并启用保守式记录缓冲
USEMYTABLE
=CURSORSETPROP("Buffering",2)
LModified=.F.
FORnFiedNum=1TOFCOUNT()&&遍历字段,检查是否有字段进行了修改。
IFGETFLDSTATE(nFieldNum)=2
LModiffied=.T.&&附注:
此代码可以在“保存”或“更新”命令按
钮的Click事件中
EXIT
ENDIF
ENDFOR
IFLmodified&&定位下一个已修改的记录
nResult=MESSAGEBOX;
("记录已被修改。
存吗?
",;
4+32+256,"数据修改")
IFnResult=7&&提交当前值并为用户提供选项,询问是否还原对
当前字段所做的修改。
=TABLEREVERT(.F.)
ENDIF
ENDIF
SKIP
IFEOF()
=MESSAGEBOX("已经在表底部〞)
SKIP-1&&SKIP确保最后一次修改内容也已写入
ENDIF
THISFORM·Refresh
12.3.3检测并解决冲突
在数据库更新操作过程中,特别是在共享环境下,用户可能希望确定哪些字段已经更改,确定已更改字段的原有值或当前值是什么,VFP的缓冲和GETFLDSTATE()、GETNEXTMODIFIED(),OLDVAL()以及CURVAL()函数可以提供这些功能。
通过它们来确定哪些字段已经更改,查找已更改数据;比较当前值、原有值及已编辑的值。
这样可以决定如何处理错误或冲突。
若要检测字段中所做的更改,在更新操作之后,使用GETFLDSTATE()函数,它可以对非缓冲数据进行操作,但在启用了记录缓冲的情况下,此函数更有效。
可将GETFLDSTATE()用于一个表单的Skip按钮代码中。
当移动记录指针时,VFP将检查记录中所有字段的状态,如例12.6所示。
例12.6:
LModified==·F·
FORnFieldNum=1TOFCOUNT()&&检查所有字段。
ifGETFLDSTATE(nFieldNum)=2&&字段已修改。
LModified=.T.&&可以在此处插入一个更新/保存例程,参
阅下一个示例。
EXIT
ENDIF
ENDFOR
若要检测并定位缓冲数据中已更改的记录,可使用GETNEXTMODIFIED()函数,它以0作为参数,查找第一个已修改的记录。
如果其他用户对缓冲表进行了修改,则你的缓冲区中TABLEUPDATE()命令遇到任何修改都将导致冲突。
可以用CURVAL()、OLDVAL()和MESSAGEBOX()计算冲突值并解决冲突,CURVAL()返回磁盘上记录的当前值,而OLDVAL()返回记录缓冲时的原有值。
在共享环境中,可以创建一个出错处理过程,比较当前值与原有值,并决定是接受用户自己的编辑结果还是将编辑结果写入原来的表之前已被其他用户修改的磁盘上的当前值。
下面的例子使用这几个函数,在更新操作过程中给用户提供选择。
这个例子检测第一个修改的记录开始,可以包含在表单的“更新”或“保存”按钮代码中。
例12.7:
“更新”或“保存”按钮Click事件代码。
DOWHILEGETNEXTMODIFIED(nCurRec)<>0&&循环遍历缓冲区。
GOnCurRec
=RLOCK()&&锁定已修改的记录。
FORnField=1TOFCOUNT(cAlias)&&查找冲突。
cField=FIELD(nField)
IFOLDVAL(cField)<>CURVAL(cField)&&比较原有值和磁盘上的当前值,
请用户指示如何处理冲突。
nResult=MESSAGEBOX("数据已被其他用户修改。
保存变化吗?
",4+48+0,"被;修改的记录")
IFnResult=7&&如果用户选择“”否按钮,则还原此记
录,然后解锁。
=TABLEREVERT(.F.)
UNLOCKRECORDnCurRec
ENDIF
ENDIF
ENDFOR
nCurRec=GETNEXTMODIFIED(nCurRec)&&查找下一个已修改的记录。
ENDDO
=TABLEUPDATE(.T.,.T.)&&对所有记录更新。
12.4用事