2012-01-26 6 views
1

私は、ファイル内のすべての行のSQLストアドプロシージャの繰り返しを行うforループを持っていますqueue.txt、今すぐすべてがうまくいく、DOESNTは何ですか?それは反復処理され、反復基準として使用されるファイルの末尾に別の行が追加され、無視されます。私が持っているものバッチForループは、それが引っ張っているファイルをリフレッシュしません

はこれです:

@echo off 
cd "%UserProfile%\Desktop\Scripting\" 
echo words > busy.txt 

FOR /f "delims=" %%a in ('type queue.txt') DO (
IF NOT EXIST reset.sql (

::Create SQL command 
echo USE dbname> reset.sql 
echo EXEC dbo.sp_ResetSubscription @ClientName = '%%a'>> reset.sql 
echo EXEC dbo.sp_RunClientSnapshot @ClientName = '%%a'>> reset.sql 
echo #################### %date% - %time% ####################################################>> log.txt 
echo Reinitialising '%%a'>> log.txt 
sqlcmd -i "reset.sql">> log.txt 
echo. >> log.txt 
echo ####################################################################################################>> log.txt 
echo. >> log.txt 

type queue.txt | findstr /v %%a> new.txt 
type new.txt> queue.txt 
echo New list of laptops waiting:>> log.txt 
type queue.txt>> log.txt 
echo. >> log.txt 
echo ####################################################################################################>> log.txt 
echo. >> log.txt 

if exist reset.sql del /f /q reset.sql 

) 
) 

if exist busy.txt del /f /q busy.txt 
if exist queue.txt del /f /q queue.txt 
if exist new.txt del /f /q new.txt

だから、これはファイルqueue.txtを引っ張ると、それらのそれぞれの反復を作るん何、今これは素晴らしいですが、それはファイル内の2行から始まることを言います、それらのためのプロシージャの実行を開始します。

ここで、別の行をqueue.txtに追加すると、そのループが実行されている間にfor行が無視されるように見えるので、一度だけインポートする各繰り返しでファイルから更新されません。

私はこれを解決すると考えていた1つの方法は、ループの最初の反復で行数を数え、各反復の終わりに値がどうあるべきかと考えてチェックし、それよりもforループの上に戻ることを期待しています(gotoなどを使用)が、ロジック式では正しく機能しません。

アドバイスありがとうございます。

答えて

3

@Myles Gray - あなたのソリューションにはいくつか問題があります。

まずマイナーな問題:!

1)キューループの各反復の後、あなたは(あなたが期待し、よりその以降で)あなたが現在作業しているオリジナルのキューマイナスラインとしてキューを再作成します。キューを再作成した後、ログに追加します。それはうまくいくでしょうが、非常に非効率的で、ログを大規模で不安定にする可能性があります。 10,000行のキューがあるとします。キューを処理するまでには、ログに49,994,999のキュー行を含めて、99,989,998のキュー行が書き込まれます。それは実際にあなたの仕事をしなくても、処理するのに長い時間がかかります。

2)現在のIDと一致しないすべての行を保存して、FINDSTRを使用してキューを再作成します。しかし、あなたの現在のIDと一致すると、後続の行も削除されます。それは問題ではないかもしれません。しかし、あなたは部分文字列のマッチングを行っています。あなたのFINDSTRは、その中のどこにでもあなたの現在のIDを含む後続の行を削除します。あなたのIDがどのように見えるか分かりません。しかし、あなたの現在のIDが123ならば、以下のIDのすべてが誤って取り除かれます - 31236、12365など。これは潜在的に致命的な問題です。私は、FORループが既にキューをバッファしているので、それは気にしないので、潜在的だと言います - 新しい作業がlate.txtファイルに追加されたのでループを中止しない限り、実際にはそれらの欠けているIDをスキップします!これは、FINDSTRに/ Xオプションを追加することで修正できます。少なくとも真の複製をスキップするだけです。

ここで重要な問題は、1つのプロセスだけが任意の種類の書き込み(または削除)操作でファイルを開くことができるということです。

3)FOR/Fループはファイルに書き込みませんが、ファイルが別のプロセスによってアクティブに書き込まれている場合、失敗するように設計されています。したがって、別のプロセスが追加されている間にFORループがキューを読み込もうとすると、キュー処理スクリプトは失敗します。 busy.txtファイルのチェックがありますが、busy.txtファイルが作成される前にキュー作成者がすでに書き込みを開始している可能性があります。特に多くの行が追加されている場合は、書き込み操作に時間がかかることがあります。行が書き込まれている間にキュープロセッサが起動し、衝突や障害が発生する可能性があります。

4)キュープロセッサがlate.txtをキューに追加し、late.txtを削除します。しかし、キューライターがlate.txtに追加の行を追加する可能性のある、追加と削除の間には時間があります。この遅れて到着した行は処理されずに削除されます!

5)ライターは、キュープロセッサによって削除されている間に、late.txtに書き込もうとする可能性があります。書き込みは失敗し、再度キューが失われます。

6)キューライターがキューに追加されている間にキューがlate.txtを削除しようとする可能性があります。削除は失敗し、次にキュープロセッサがqueue.txtにlate.txtを追加したときに、キューに重複してしまいます。

要約すると、並行性の問題は、キュー内での作業の欠落と、キュー内での作業の重複の両方につながります。ファイルを同時に変更する複数のプロセスがある場合は、イベントをシリアライズするための何らかのロック機構を確立する必要があります。

既にSqlServerデータベースを使用しています。最も論理的なことは、キューをファイルシステムからデータベースに移動することです。リレーショナルデータベースは、同時性を扱うために根本から構築されています。

ロック戦略を採用している限り、Windowsバッチ内のファイルとしてキューを使用することは難しくありません。キュープロセッサとキューライタの両方が同じロック戦略に従っていることを確認する必要があります。

以下はファイルベースのソリューションです。私はあなたが1つのキュープロセッサと複数のキューライタしか持っていないと仮定します。追加作業により、ファイルキューソリューションを複数のキュープロセッサをサポートするように変更することができます。しかし、複数のキュープロセッサはおそらく、my first answerの最後に記述したフォルダベースのキューを使用して実装する方が簡単でしょう。

キューの作成者にqueue.txtまたはlateのどちらかを書き込ませる代わりに、既存のキューの名前を変更してキューの処理を完了させる方が、キューの作成者は常にqueue.txtに書き込む方が簡単です。

このソリューションは、現在のステータスをstatus.txtファイルに書き込みます。コマンドウィンドウからTYPE STATUS.TXTを発行すると、キュープロセッサの状態を監視できます。

データ内で!が原因で破損しないようにするために、拡張拡張トグルを行います。 !が表示されないことがわかっている場合は、SETLOCAL EnableDelayedExpansionを一番上に移動して、切り替えを断つことができます。

その他の最適化 - 各文のファイルを開いたり閉じたりするのではなく、文のグループに対して出力を1回だけリダイレクトする方が高速です。

このコードは完全にテストされていないので、簡単にバグがある可能性があります。しかし、そのコンセプトは健全です。うまくいけば、アイデアを得ることができます。

queueProcessor.bat

@echo off 
setlocal disableDelayedExpansion 
cd "%UserProfile%\Desktop\Scripting\" 

:rerun 

::Safely get a copy of the current queue, exit if none or error 
call :getQueue || exit /b 

::Get the number of lines in the queue to be used in status updates 
for /f %%n in ('find /v "" ^<inProcess.txt') do set /a "record=0, recordCount=%%n" 

::Main processing loop 
for /f "delims=" %%a in (inProcess.txt) do (

    rem :: Update the status. Need delayed expansion to access the current record number. 
    rem :: Need to toggle delayed expansion in case your data contains ! 
    setlocal enableDelayedExpansion 
    set /a "record+=1" 
    > status.txt echo processing !record! out of %recordCount% 
    endlocal 

    rem :: Create SQL command 
    > reset.sql (
    echo USE dbname 
    echo EXEC dbo.sp_ResetSubscription @ClientName = '%%a' 
    echo EXEC dbo.sp_RunClientSnapshot @ClientName = '%%a' 
) 

    rem :: Log this action and execute the SQL command 
    >> log.txt (
    echo #################### %date% - %time% #################################################### 
    echo Reinitialising '%%a' 
    sqlcmd -i "reset.sql" 
    echo. 
    echo #################################################################################################### 
    echo. 
) 
) 

::Clean up 
delete inProcess.txt 
delete status.txt 

::Look for more work 
goto :rerun 

:getQueue 
2>nul (
    >queue.lock (
    if not exist queue.txt exit /b 1 
    if exist inProcess.txt (
     echo ERROR: Only one queue processor allowed at a time 
     exit /b 2 
    ) 
    rename queue.txt inProcess.txt 
) 
)||goto :getQueue 
exit /b 0 

queueWriter.bat

::Whatever your code is 
::At some point you want to append a VALUE to the queue in a safe way 
call :appendQueue VALUE 
::continue on until done 
exit /b 

:appendQueue 
2>nul (
    >queue.lock (
    >>queue.txt echo %* 
) 
)||goto :appendQueue 

ロックコードの説明:

:retry 
::First redirect any error messages that occur within the outer block to nul 
2>nul (

    rem ::Next redirect all stdout within the inner block to queue.lock 
    rem ::No output will actually go there. But the file will be created 
    rem ::and this process will have a lock on the file until the inner 
    rem ::block completes. Any other process that tries to write to this 
    rem ::file will fail. If a different process already has queue.lock 
    rem ::locked, then this process will fail to get the lock and the inner 
    rem ::block will not execute. Any error message will go to nul. 
    >queue.lock (

    rem ::you can now safely manipulate your queue because you have an 
    rem ::exclusive lock. 
    >>queue.txt echo data 

    rem ::If some command within the inner block can fail, then you must 
    rem ::clear the error at the end of the inner block. Otherwise this 
    rem ::routine can get stuck in an endless loop. You might want to 
    rem ::add this to my code - it clears any error. 
    verify >nul 

) && (

    rem ::I've never done this before, but if the inner block succeeded, 
    rem ::then I think you can attempt to delete queue.lock at this point. 
    rem ::If the del succeeds then you know that no process has a lock 
    rem ::at this point. This could be useful if you are trying to monitor 
    rem ::the processes. If the del fails then that means some other process 
    rem ::has already grabbed the lock. You need to clear the error at 
    rem ::this point to prevent the endless loop 
    del queue.lock || verify >nul 

) 

) || goto :retry 
:: If the inner block failed to get the lock, then the conditional GOTO 
:: activates and it loops back to try again. It continues to loop until 
:: the lock succeeds. Note - the :retry label must be above the outer- 
:: most block. 

あなたはユニークなプロセスIDをお持ちの場合、あなたはそれを書くことができます内部ブロック内のqueue.lock。次に、別のウィンドウからqueue.lockと入力して、現在どのプロセスがロックを持っているか(または最近持っているか)調べることができます。これは、プロセスがハングアップした場合にのみ問題になるはずです。

+0

うわー、私は素晴らしい仕事をすることができ、あなたはこれのための恩恵を受けることができます、それは信じられないほどの仕事です、私はこれを介して作業し、物事があなたのソリューションを使用して行く方法を参照してくださいしかし、明日あなたのために+100! –

+0

@MylesGray - クール。賞金は予期せぬ甘いボーナスです - 私の答えが最も多くの投票を取ると仮定します:)コードは、1980年代にキューベースの仕事をしていたので、それほど難しくありませんでした。しかし説明には時間がかかります。 * Note * - 最初に投稿してからqueueWriter(条件付きのgoto)の最後にバグ修正を追加しました。 – dbenham

+0

私はそれを早期に終了して、とにかくそれを授与することができます - それは私が待っているものです(私ができるようになるまでには24時間かかります)。フォルダベースのソリューションでは、私は単にinProcess.txt、queue.txtなどをフォルダと入れ替えることを前提としていますか? '2> nul(> queue.lock'がどういうふうに動作するか説明できますか? –

2

あなたは本当に正しいです - FOR/Fループは、IN()句のコマンドが終了するのを待って、1行目を処理する前に結果をバッファします。コマンドを実行する代わりに、IN()句内のファイルから読み込んだ場合も同様です。

FORループの前にキュー内の行数をカウントし、FORループが完了した後に再カウントするという提案された方法は、FORループ内のキューの内容で停止すると機能します。最終的なカウントがオリジナルより大きい場合は、FORループの前にGOTO a:ラベルを付けて、FORループ内の元の行数をスキップして、追加された行だけを処理します。しかし、プロセスが行数を取得している間にプロセスがキューに書き込んだ場合、または最終的なカウントを取得した後でキューを削除する前にキューに追加された場合でも、並行性の問題が発生します。

複数のプロセスを処理する場合、バッチ内でイベントをシリアライズする方法があります。これを行うための鍵は、1つのプロセスだけが書き込みアクセス用にファイルを開くことができるという事実を利用することです。

排他的な「ロック」を確立するために、次のようなコードを使用できます。 プロセスでは同じロジックが使用されている限り、コードブロックを終了してロックを解除するまで、1つ以上のファイルシステムオブジェクトを排他的に制御できます。

:getLock 
2>nul (
    >lockName.lock (
    rem ::You now have an exclusive lock while you remain in this block of code 
    rem ::You can safely count the number of lines in a queue file, 
    rem ::or append lines to the queue file at this time. 
) 
)||goto :getLock 

Re: parallel process with batchでこれがどのように機能するかを実証しました。リンクを押した後、上にスクロールして元の質問を表示します。それはあなたと非常によく似た問題のようです。

フォルダではなくファイルとしてキューを使用することをお勧めします。各作業単位は、フォルダ内のそれ自身のファイルにすることができます。ロックを使用して、各作業単位の命名に使用するファイル内のシーケンス番号を安全にインクリメントすることができます。完成した作業単位は、 "preperation"フォルダで準備し、完了後に "queue"フォルダに移動するだけで、作業単位が完全に書き込まれたことを保証できます。この戦略の利点は、処理中に各作業単位ファイルを「inProcess」フォルダに移動し、処理が完了した時点で削除またはアーカイブフォルダに移動できることです。処理が失敗した場合、ファイルはまだ "inProcess"フォルダに存在するため、回復することができます。どの作業単位がまだ処理されていないか(「キュー」フォルダにまだ残っている)と同様に、不安定な作業単位(「inProcess」フォルダ内の死んだもの)と作業単位を知る立場にあります。

+0

大変ありがとうございましたが、私のスクリプトで何かが死んでしまった場合は、もっと素直なフォレンジを見つけました。 –

0

私が働いた私の問題へのsoultionがco-ordinator.batと呼ばれる余分なバッチファイルを追加することでしたので、それはbusy.txtが存在した場合、それはその後だった場合は、各の末尾にファイルlate.txtに接続するデバイスを追加します確認オーケープロセスが存在するかどうかを確認するループの反復が存在する場合は、queue.txtとマージし、gotoをループの先頭に使用してforループを再初期化します。

のようなコード:

@echo off 
cd "%UserProfile%\Desktop\Scripting\" 
echo words > busy.txt 
:rerun 

FOR /f "delims=" %%a in ('type queue.txt') DO (
IF NOT EXIST reset.sql (

::Create SQL command 
echo USE dbname> reset.sql 
echo EXEC dbo.sp_ResetSubscription @ClientName = '%%a'>> reset.sql 
echo EXEC dbo.sp_RunClientSnapshot @ClientName = '%%a'>> reset.sql 
echo #################### %date% - %time% ####################################################>> log.txt 
echo Reinitialising '%%a'>> log.txt 
sqlcmd -i "reset.sql">> log.txt 
echo. >> log.txt 
echo ####################################################################################################>> log.txt 
echo. >> log.txt 

type queue.txt | findstr /v %%a> new.txt 
type new.txt> queue.txt 
echo New list of laptops waiting:>> log.txt 
type queue.txt>> log.txt 
echo. >> log.txt 
echo ####################################################################################################>> log.txt 
echo. >> log.txt 

if exist reset.sql del /f /q reset.sql 
if exist late.txt (
type late.txt>> queue.txt 
del /f /q late.txt 
goto rerun 
) 
) 
) 

if exist late.txt del /f /q late.txt 
if exist busy.txt del /f /q busy.txt 
if exist queue.txt del /f /q queue.txt 
if exist new.txt del /f /q new.txt 
+1

このソリューションには重大な問題があります。詳細は、私の[第2の回答](http://stackoverflow.com/a/9048097/1012053)を参照してください。 – dbenham

1

「別の行がファイルの一番下にを追加された場合...」あなたはあなたの質問に入れ、しかし、あなたのコードはに行を追加しませんが、(新しいコンテンツが一つだけ新しい行が追加されていますが)完全にファイル全体の内容を置き換えます:あなたはキューを処理する方法を変更することがあり

FOR /f "delims=" %%a in ('type queue.txt') DO (
    IF NOT EXIST reset.sql (

    . . . 

    type queue.txt | findstr /v %%a> new.txt 
    rem Next line REPLACES the entire queue.txt file! 
    type new.txt> queue.txt 
    echo New list of laptops waiting:>> log.txt 

    . . . 

    if exist reset.sql del /f /q reset.sql 

    ) 
) 

。 txtファイルを、SET/Pコマンドを介してその行を読み込むサブルーチンと、GOTOでアセンブルされたループにリダイレクトすることによって実行します。このようにして、読み込みループ内のqueue.txtファイルの最後に追加される行は、読み込みプロセスがそれらに達すると直ちに読み込まれます。新しい行は、他のプロセスによってを追加している場合はもちろん

call :ProcessQueue <queue.txt>> queue.txt 
goto :EOF 


:ProcessQueue 
    set line= 
    rem Next command read a line from queue.txt file: 
    set /P line= 
    if not defined line goto endProcessQueue 
    rem In following code use %line% instead of %%a 
    IF NOT EXIST reset.sql (

    . . . 

    type queue.txt | findstr /v %%a> new.txt 
    rem Next command ADD new lines to queue.txt file: 
    type new.txt 
    echo New list of laptops waiting:>> log.txt 

    . . . 

    if exist reset.sql del /f /q reset.sql 

    ) 
goto ProcessQueue 
:endProcessQueue 
exit /B 

は、新しい行が読み込まれ、自動的にこのバッチファイルによって処理されます。

この方法は、queue.txtファイルの最初の空行で終了することに注意する必要があります。処理できる文字にもいくつかの制限があります。

EDIT

Original first line 
Original second line 
Original third line 
Original fourth line 

これが結果です:

これは、入力でqueue.txtファイルです

set i=0 
call :ProcessQueue <queue.txt>> queue.txt 
goto :EOF 

:ProcessQueue 
    set line= 
    set /P line= 
    if not defined line goto endProcessQueue 
    echo Line processed: %line% > CON 
    set /A i=i+1 
    if %i% == 1 echo First line added to queue.txt 
    if %i% == 2 echo Second line added to queue.txt 
goto ProcessQueue 
:endProcessQueue 
exit /B 

:これはどのようにこのメソッドの仕事を示して簡単な例であります

Line processed: Original first line 
Line processed: Original second line 
Line processed: Original third line 
Line processed: Original fourth line 
Line processed: First line added to queue.txt 
Line processed: Second line added to queue.txt 
+0

ファイル全体が置き換えられることは知っていますが、そのセクションは 'late.txt'をチェックして新しい行を追加します'new.txt'に' queue.txt'ファイルを追加し、 'new.txt'に現在の' queue.txt'ファイルを追加します。 'queue.txt'を' new.txt'の内容で上書きします。 –

+0

@MylesGray: _replace_ではなく、 'queue.txt'ファイルに新しい行を_append_する必要があります。このメソッドは、 'queue.txt'ファイルを読み込むプロセスとそれに新しい行を追加する他のプロセスとの間のすべての並行性の問題を完全に避けるため、面白いです。単純で高速です。小さなサンプルを追加して、私の方法が何をすることができるかを見てみましょう。この例は元の質問に基づいています。 – Aacini

関連する問題