題 如何防止兩次運行相同的批處理文件以僅允許一個實例?


我需要檢測批處理文件是否已經運行。

在批處理中,我只需循環並重複執行一些任務。有時循環無法完成。我在預定的時間運行批處理文件。當它運行時,它應檢查先前的實例是否存在並且是否健康。如果它不存在,它應該啟動一個新實例。如果存在的實例不健康/過時,則新實例應該終止/取消舊進程。

一些理論:

  1. 如何檢測批處理文件中的先前實例?我可以提供身份證嗎?
  2. 我可以使用脈衝機制來表示批次是健康的;即,全局變量,新創建的虛擬文件,虛擬文件中的時間戳等。
  3. 如果最後一個脈衝到期,我可以檢測到一種“超時”,然後允許一個新實例......

是的,我都希望它們在一個批處理文件中 - 而不是一個Windows應用程序 - 我認為對於那個批處理文件成癮的人來說並不難,並將這個問題作為挑戰!


4
2017-11-28 14:53


起源


你在這裡檢查了我的答案: superuser.com/questions/362164/... - stijn
對了謝謝。我需要的不僅僅是識別正在運行的批處理;即。多個實例怎麼樣?我能告訴他們一個名字嗎?單獨殺死他們?我不想從任務管理器手動執行此操作。 - Nime Cloud
不確定你的意思 - 你的問題是關於允許單個實例。但現在你問的是多個實例?無論如何殺死他們可以使用taskkill或pskill完成 - stijn
單個實例#1:monitor.bat domain1.com單個實例#2:monitor.bat domain2.com - Nime Cloud
這不是批處理文件本身的單個實例。如果將標題設置為,您仍然可以使用標題技巧 mybatch domain1.com  mybatch domeain2.com 或者使用文本文件,在這些文件中保存一個長期運行更好的列表。 - stijn


答案:


從批處理文件的角度來看,這是相當困難的,主要是因為沒有保證批處理文件檢測跨進程的實例的方法。

IMO,我認為你使用的是錯誤的工具。雖然,是的,這是一個挑戰,更重要的是“它有用嗎?”

我在預定的時間運行批處理文件。

這告訴我你應該使用一個調度程序,比如Task Scheduler。任務計劃程序將保證您的文件只有一個實例,即使它在另一個用戶上運行。

例如,如果每30分鐘運行一次任務,則將任務配置為運行30分鐘。然後將“如果任務已在運行”設置為“不創建新實例”。或者你必須選擇“殺死舊的”,“運行另一個實例”,或“在舊的任務完成後立即開始新的任務”。

對於Vista及以上版本: http://technet.microsoft.com/en-us/library/cc748993.aspx#BKMK_cmd
對於Xp及以上: http://technet.microsoft.com/en-us/library/bb490996.aspx

具有挑戰性的方法是創造國家。

  1. 批處理文件進入執行狀態
  2. 您的批處理文件循環
  3. 您的批處理文件結束

您可以在%allusersprofile%中創建任意文件,例如 >>Startmybatchfile.txt echo 1 對於每個州,然後檢查它是否存在。它也有助於解決問題。但是不要在最後刪除文件。只需將其更改為End State即可。這樣,如果您的批處理文件有確定性,那就是確定性的  完成,不再運行。

我不會使用全局變量,因為它需要管理員權限才能運行。

您也可以使用%temp%變量,但是從用戶更改為用戶。如果您是一台登錄用戶計算機,我想這不是問題。


6
2017-11-28 16:18



如果所有實例都終止,則TS啟動新實例。這就是我使用TS的原因。此外,當OS啟動時,TS會創建第一個實例,等等。 - Nime Cloud


這是我最新,最值得信賴的腳本:

:init
set "started="
2>nul (
 9>"%~f0.lock" (
  set "started=1"
  call :start
 )
)
@if defined started (
    del "%~f0.lock" >nul 2>nul
) else (
    echo Process aborted: "%~f0" is already running
    @ping localhost > nul
)

exit /b




:start
cd /d %~dp0
:: REST OF THE SCRIPT

5
2017-07-16 06:15



這個腳本發生了什麼?我們可以在多個批處理腳本中使用它嗎? - Angelina


我發現這樣做的最好方法是在批處理腳本上設置標題。

從那裡,您可以使用任務列表按標題名稱查詢正在運行的進程的計數。以下代碼片段在process_count = 2以下的任何內容表示進程未運行,3表示正好運行1個實例,如果超過3,則運行的進程超過3個。

Hackish,但批處理就是它。

對於一個持續循環的腳本,我不夠聰明,想到一種方法可以將開始時間寫入標題字段並從另一個批處理腳本中查詢。我曾想過用時間複製和重命名文件名然後執行它,但對我來說除了直接查詢之外的任何東西都比它的價值更糟糕。

是的我可以刪除文本文件,但我擔心緩存損壞。

@REM ########################
:COUNT_PROCESS_INSTANCES
@REM @@@@@@@@@@@@@@@@@@@@@@@@

SET /A PROCESS_COUNTER=0


@REM No way to inject a counter into this for statement.
FOR /F "DELIMS=" %%A in ('tasklist /FI "WINDOWTITLE EQ %USERNAME%:  %PROCESS_NAME_TO_COUNT%*"') do (CALL :SUB_INCRIMENT_PROCESS_COUNTER)

@REM If one instance is running, or no instances, that's fine.  If more than one instance is running, time to exterminate.

@ECHO PROCESS COUNTER IS: "%PROCESS_COUNTER%"
SET PROCESS_NAME_TO_COUNT=NULL

GOTO :EOF

:SUB_INCRIMENT_PROCESS_COUNTER
SET /A PROCESS_COUNTER=%PROCESS_COUNTER%+1
GOTO :EOF

@REM @@@@@@@@@@@@@@@@@@@@@@@@
@REM ########################

此外,對任何感興趣的人;這是如何找到當前正在執行的進程的PID。現在,如果您將一個變量傳遞給一個子腳本,您可以通過文本文檔將該變量傳遞回鏈(我認為還有另一種方法可以做到這一點,但這讓我感到厭煩)。

@REM @@@@@@@@@@@@@@@@@@@@@@@@
:FIND_SELF_PID
@REM ########################
@REM Leaving this here for later if needed.  This was a byproduct of a bad approach.

FOR /F "TOKENS=1,2,*" %%A in ('tasklist /FI "WINDOWTITLE EQ %USERNAME%:  %CURRENT_TITLE%"') do (SET SELF_PID=%%B)

echo %self_pid%

GOTO :EOF
@REM @@@@@@@@@@@@@@@@@@@@@@@@
@REM ########################

2
2017-07-14 21:33





關於問題的第一部分,如果您使用tasklist.exe來查找進程,請注意“windowtitle”篩選器僅適用於單獨進程中的窗口。因此,如果您想讓批處理文件檢測到ITSELF(即檢查是否已有正在運行的實例),則應使用'imagename'代替:

tasklist /V /NH /FI "imagename eq cmd.exe"| find /I /C "UNIQUETITLE" > nul

另請注意,如果沒有過濾器,則可能需要在Windows的更高版本上花費很長時間。 以下是批處理文件的示例,該文件一次只能作為一個實例運行:

@echo off
:: Test with tasklist.exe to make sure the process is not already running:
:: Window title must be unique.
set loop_batch_title=Only this instance is allowed to run.
:: Run tasklist in verbose mode (which returns window titles) and search for the window title of this batch file:
tasklist /V /NH /FI "imagename eq cmd.exe"| find /I /C "%loop_batch_title%" > nul
:: If the window title is found then the errorlevel variable will be 0, which means the process is already running:
if %errorlevel%==0 goto AlreadyRunning
:: If the process isn't already running then the window title can be set:
title %loop_batch_title%

::Put your program loop here
:Loop
echo This is a loop.
ping -n 3 127.0.0.1>nul
goto Loop

:AlreadyRunning
echo.
echo Process already running.
endlocal

1
2017-09-15 07:30





我剛剛完成批處理文件,其中一個確保監視本身 - 批處理文件 - 和作業文件-facerecog.exe-運行順利。我用hello.exe作為測試探針工具,你可以替換自己的;即。 ping.exe等在沒有任何參數的情況下啟動它會觸發另一個實例並退出,因此您可以在特定的時間間隔內將其作為計劃任務運行。我希望它可以幫助那些不想使用高級工具的人。

    @goto code

    :comments
    @REM RunMagick for FaceRecog
    @REM Runs FaceRecog forever
    @REM Author Emin Akbulut eminakbulut@gmail.com
    @REM 29/11/2011
    @REM This code is freeware


    :code
    @C:
    @CD C:\NET\FaceRecog

    :settings
    set jobexe=FaceRecog.exe
    set remotehost=192.168.35.211
    set remoteport=1333
    @REM Timeout for response of the batch, in seconds
    set /a timeout=120
    set batchtitle=Monitoring FaceRecog...


    :requirements
    @if NOT exist "stopwatch.exe" (
    @echo. stopwatch.exe is missing!
    @ping localhost > nul
    start http://www.jfitz.com/dos/stopwatch10.zip
    goto end
    ) 




    :menu
    @if "%1" == "/monitor" goto monitor
    @if "%1" == "/stop" goto request-stop
    @if "%1" == "/force-stop" goto stop


    :batch-instance-check 
    tasklist /v /fi "IMAGENAME eq cmd.exe" | find /I /c "%batchtitle%"
    @if "%ERRORLEVEL%" == "0" goto batch-instance-found
    goto start

    :batch-instance-found
    @REM Check the previous instance is healthy/responsive or not
    stopwatch stop < pulse.log > elapsed.log
    set /a elapsed=999
    for /F %%a in ('type elapsed.log') do set /a elapsed=%%a
    if %elapsed% gtr %timeout% (
    taskkill /F /FI "WINDOWTITLE eq %batchtitle%*"
    goto start
    ) else (
    @echo Already running...
    @ping localhost > nul
    goto end
    )

    del elapsed.log



    @goto monitor



    :run-job
    @REM Test FaceRecog process
    tasklist | Find "FaceRecog.exe"
    @If Not ErrorLevel 1 Goto monitor

    taskkill /f /im:conime.exe
    @PING 1.1.1.1 -n 1 -w 1000 >NUL
    start FaceRecog.exe
    @REM Take a breathe
    @PING 1.1.1.1 -n 1 -w 1000 >NUL
    @goto run-job

    :start
    @REM Fire another instance with /monitor command and exit
    start "%batchtitle%" /low /I %0 /monitor
    goto end

    :stop
    @REM Stop the job
    taskkill /F /IM:%jobexe%
    @REM Stop the batch
    taskkill /F /FI "WINDOWSTITLE eq %batchtitle%*"
    del stop.log > nul
    title Done
    goto end

    :request-stop
    @REM Ask programs to quit
    @REM %jobexe% /quit
    @REM Take a breathe
    @ping localhost > nul
    stopwatch start > stop.log
    goto end

    :monitor
    stopwatch start > pulse.log
    @REM Test /stop requested
    @if exist stop.log goto stop
    @REM Test FaceRecog process
    tasklist | Find "%jobexe%"
    @If Not ErrorLevel 0 Goto run-job

    :shorttest
    @Echo Shorttest...
    hello.exe %remotehost% %remoteport% | Find "Version" 
    @If Not ErrorLevel 1 Goto job-ok

    :crashcheck
    @REM No response, crashed?
    tasklist | Find "WerFault.exe"
    @If ErrorLevel 1 Goto longcheck
    taskkill /F /IM:WerFault.exe
    @goto shorttest

    :longcheck
    @Echo Longtest...
    @REM Last check, maybe FaceRecog was busy
    hello.exe %remotehost% %remoteport% | Find "Version" 
    @If Not ErrorLevel 1 Goto job-ok

    :nonresponsive
    @REM Kill non-responsive FaceRecog process and wait for a while to auto-revive
    taskkill /F /IM:%jobexe%



    :job-ok
    @echo OK
    @REM 10 seconds to wait then loop again
    @PING 1.1.1.1 -n 1 -w 10000 >NUL
    @goto monitor


    :end

謝謝 斯泰恩 他的好評。 謝謝 surfasb 關於已經運行的計劃任務的警告


0
2017-11-29 14:24