2017年3月7日火曜日

断続的に発生しうる処理を一時的に貯める関数

一つのファイルに対して複数の読み書き要求が同時に発生しうる場合、先日公開したファイルロック.htaを使えば良いのですが、先日のソースではファイルに対する処理のタイミングが他人ではなく自分自身とも競合してしまい待ち時間が長くなってしまう状態でした。

例えばZ:\TEMP\lockTest.txtに対してアクセスしているのが自分ひとりだけだったとしても、数秒以内に複数の項目を書き換えるとそれぞれの操作に対して個別にファイルロック機能が働いていました。

連続した操作による変更なら出来るだけまとめた方が、ファイルのアクセス権獲得のためにロック試行を繰り返したりロックできなくて待機したりという時間を減らせます。

本日のソースでは変更操作を行ってもすぐにはファイルをロックせず、数秒以内に次の操作が行われればそこからまた待機時間を設けて期限が切れたらファイルをロックして変更を反映します。
以下ソース
<html>
<title>lock</title>
<body style="text-align:center;overflow:hidden;padding:0px;margin:0px;">
<input id=inp style="width:100%;text-align:center">
<button id=btnL>load</button>
<button id=btnI>init</button>
<table border=1 style="width:100%;">
<tr><td width=1>A</td><td><input id=inpA style="width:100%"></td></tr>
<tr><td width=1>B</td><td><input id=inpB style="width:100%"></td></tr>
<tr><td width=1>C</td><td><input id=inpC style="width:100%"></td></tr>
</table>
<table id=tbl style="position:absolute;top:0px;left:0px;width:100%;height:100%;background-color:#ccc"><td style="text-align:center">初期化中。。少々お待ちください</td></table>
</body>
<script id=scriptCode共有用>
AXO=function(name){return new ActiveXObject(name)}
shell = AXO('WScript.Shell')
fso=function(){
var fs=AXO('Scripting.FileSystemObject')
return {
AXO:fs,
gsf2:fs.GetSpecialFolder(2), // Z:\TEMP
Read:function(path, swCreate, swUnicode){
if(!fs.FileExists(path)){return}
with(fs.OpenTextFile(path, 1, swCreate, swUnicode)){ var str=AtEndOfStream ? '' : ReadAll(); Close() }
return str
},
Write:function(path,str,swUnicode,sw上書きしない){ with(fs.CreateTextFile(path, !sw上書きしない, swUnicode)){ Write(str); Close() } },
Lock:function(path){
var pathL=path+'.lock', ws, wsn=AXO('WScript.Network')
try{
ws=fs.CreateTextFile(pathL)
ws.Write(wsn.computerName+'\r\n'+wsn.userName)
return {Close:function(){ ws.Close(); fs.DeleteFile(pathL) }}
}
catch(e){
return this.Read(pathL)
}
}
}
}()
PID=function(){
// プロセス起動中にPIDが更新されることは無いので一度取得したら取得用関数は不要なのでPIDだけ保持する。
var f=function(){
// 子プロセスをcscriptにすると黒い画面が一瞬表示されてしまう。
// 「//B」オプションを付けないとwscriptの設定画面が表示されてしまう。
var childPID = shell.Exec('wscript //B').ProcessID
// 子の親(自身)のPIDを特定する
var res = new Enumerator(GetObject("winmgmts:root\\CIMV2").ExecQuery("SELECT * FROM Win32_Process where ProcessID="+childPID))
return res.atEnd() ? (void 0) : res.item().ParentProcessID
}
// 1度目で取得に成功すればその値を返し、ダメなら2回目の処理を実行する。
return f() || f()
}()
</script>
<script>
resizeTo(200,200)
inp.value = fso.gsf2+'\\lockTest.txt'
有ったら消す = function(path){ fso.AXO.FileExists(path) && fso.AXO.DeleteFile(path) }
obj = null
loadObj=function(){
try{ eval('obj = '+fso.Read(inp.value)) }
catch(e){ return }
inpA.value = obj.A
inpB.value = obj.B
inpC.value = obj.C
return obj
}
btnL.onclick=function(){
var path=inp.value
if(!fso.AXO.FileExists(path)){return alert('ファイルがありません')}
if(!loadObj()){ return alert('ファイルが破損しています') }
}
btnI.onclick=function(){
tbl.style.display = 'block'
if(loadObj()){return tbl.style.display = 'none'}
wsf.negoWrite(inp.value, function(path){ fso.Write(path, "{A:'', B:'', C:''}") }, function(){
tbl.style.display = 'none'
btnL.onclick()
})
}
onload=function(){ btnI.onclick() }
inpA.onchange=inpB.onchange=inpC.onchange=function(){
var fun=function(path){
eval('o = '+fso.Read(path))
o.prop = 'VVV'
fso.Write(path, "{A:'"+o.A.replace(/'/g,"\'")+"', B:'"+o.B.replace(/'/g,"\'")+"', C:'"+o.C.replace(/'/g,"\'")+"'}")
}
return function(){
var chr=this.id.charAt(3), obj
wsf.negoWrite(inp.value, (''+fun).replace(/prop/,chr).replace(/VVV/,this.value.replace(/'/g,"\'")), loadObj)
}
}()
wsf=function(){
var pathDQ=function(path){return 0<path.indexOf(' ') ? ('"'+path+'"') : path }
var getTime=function(dat){return (dat || (new Date())).getTime()}
return {
newWSF:function(fun){
var path=fso.gsf2+'\\'+getTime()+'.wsf'
fso.Write(path, ['<-job>',scriptCode共有用.outerHTML,'<-script>','!'+fun+'()','<-/script>','<-/job>'].join('\r\n').replace(/<-/g,'<'))
return path
},
終了後ファイル削除:function(path){
var m=arguments.callee
if(!m.wsf){
m.wsf = this.newWSF(function(){
arg = WScript.Arguments
PID = arg(0)
path = arg(1)
while(true){
WScript.Sleep(1000)
var res = new Enumerator(GetObject("winmgmts:root\\CIMV2").ExecQuery("SELECT * FROM Win32_Process where ProcessID="+PID))
if(res.atEnd()){break}
}
arr = fso.Read(path).split('\r\n')
for(i=0,L=arr.length;i<L;i++){ fso.AXO.DeleteFile(arr[i]) }
})
fso.Write(m.path = m.wsf+'.list', [m.wsf, m.path].join('\r\n'))
shell.Run('cmd /C '+pathDQ(m.wsf)+' '+PID+' '+pathDQ(m.path), 0)
}
var arr=fso.Read(m.path).split('\r\n')
arr.push(path)
fso.Write(m.path, arr.join('\r\n'))
},
negoWrite:function(path, fun, fun終了後){
// path…整合性を維持したいファイルのpath
// fun …ロック成功したら「fun(path)」の形で実行される関数
// fun終了後…上記funの完了をhta側で検知したら実行される関数。
// ただしwindow.onbeforeunloadが発生した場合はこの処理は省略される(実行前にhtaが終了してしまう)ので
// ファイルの後始末などはこの関数では行わないこと。(hta画面上の表示内容変更などを行うための関数)
var m=arguments.callee
if(!m.wsf){
m.wsf = this.newWSF(function(){
arg = WScript.Arguments
path = arg(0)
pathFunc = arg(1)
pathLock = path+'.lock'
wsn = AXO('WScript.Network')
eval('arrF = '+fso.Read(pathFunc))
while(true){
try{
lock = fso.AXO.CreateTextFile(pathLock)
lock.Write(wsn.computerName+'\r\n'+wsn.userName)
break
}
catch(e){ WScript.Sleep(1000 * 10 * Math.random()) }
}
for(i=0,L=arrF.length;i<L;i++){ arrF[i](path) }
lock.Close()
try{
fso.AXO.DeleteFile(pathFunc)
fso.AXO.DeleteFile(pathLock)
}catch(e){}
WScript.Echo('OK')
})
this.終了後ファイル削除(m.wsf)
m.obj={}
}
// pathに対するfunを一時的に保管しておくための配列が未作成なら作成する。
if(!m.obj[path]){ m.obj[path] = [] }
m.obj[path].push(fun)
m.obj[path].保留開始 = getTime()
var 保留期間 = 1000 * 10
var fun期間経過後=function(){
// 保留期間中に保留開始時刻が更新された場合は、更新時に新しいsetTimeoutが実行されているので古い方は終了する。
if(getTime() < (m.obj[path].保留開始+保留期間)){return}
var pathF=fso.gsf2+'\\fun'+getTime()+'.js', pathO=pathF+'.txt'
var リダイレクト = fun終了後 ? (' > '+pathDQ(pathO)) : ''
fso.Write(pathF, '['+m.obj[path]+']')
shell.run('cmd /C cscript '+pathDQ(m.wsf)+' '+pathDQ(path)+' '+pathDQ(pathF)+リダイレクト, 0)
if(!fun終了後){return}
var objIV=setInterval(function(){
if(!fso.AXO.FileExists(pathO)){return}
try{fso.AXO.DeleteFile(pathO)}catch(e){return}
fun終了後()
clearInterval(objIV)
},1000)
}
setTimeout(fun期間経過後, 保留期間)
// ウィンドウのonbeforeunloadイベントが発生したら即時実行されるようにしておく。
window.attachEvent('onbeforeunload', function(){
m.obj[path].保留開始 = fun終了後 = 0
fun期間経過後
})
}
}
}()
</script>
</html>

0 件のコメント:

コメントを投稿