多執行緒的概念

十秒特效

  • 做法一:

    function ExecuteCode takes code whichcode returns nothing
        local trigger T = CreateTrigger()
        local triggeraction A = TriggerAddAction(T, whichcode)
        call TriggerExecute(T)
        call TriggerRemoveAction(T,A)
        call DestroyTrigger(T)
    endfunction
    
    function AddTimeredEffectTargetUnit_Child takes nothing returns nothing
        local effect timeredeffect = bj_lastCreatedEffect
        local real duration = bj_enumDestructableRadius
        call PolledWait( duration )
        call DestroyEffect( timeredeffect )
    endfunction
    
    function AddTimeredEffectTargetUnit takes string modelName, unit whichUnit, 下行接續
        string whichAttach, real duration returns nothing
        local real Bj_enumDestructableRadius = bj_enumDestructableRadius
        set bj_lastCreatedEffect = AddSpecialEffectTarget(modelName, whichUnit, whichAttach )
        set bj_enumDestructableRadius = duration
        call ExecuteCode(function AddTimeredEffectTargetUnit_Child)
        set bj_enumDestructableRadius = Bj_enumDestructableRadius
    endfunction
    

     

  • 做法二:

    function AddTimeredEffectTargetUnit takes string modelName, unit whichUnit, 下行接續
        string whichAttach, real duration returns nothing
        local effect timeredeffect = AddSpecialEffectTarget(modelName, whichUnit, whichAttach )
        call PolledWait(duration)
        call DestroyEffect( timeredeffect )
    endfunction
    

以上兩種寫法,有什麼不同?哪一個好?乍看之下,第二種寫法似乎比較簡單,但是真的是這樣嗎?

讀者可以試試看,連續對2個不同部隊執行這個函數。你會發現,使用第一種做法,2個部隊身上會同時冒出特效,10秒後同時消失;使用第二種做法時,只有一個部隊身上冒出特效,等它消失後,第二個部隊身上才冒出特效。

所謂執行緒(Thread,或譯為線程),指的是一組程式碼的排程 ,不同的執行緒可以獨立作業而不互相干擾。每一道執行緒就像是一條線,多執行緒就好像多條平行線同時進行,不會互相干擾。

做法一便是多執行緒的寫法,一般我們在一個函數裡直接放wait時,必須等待函數執行完,才能執行下一個函數。為了免去時間延遲,我們可以利用前面的這個函數,使它成為一道獨立執行的程序。

然而code不能傳送參數,因此這裡我們借用blizzard.j裡的變數充當參數傳送,借用時要特別注意借用的時段內是否有可能再使用到同一個變數,應自行避免此情形,否則會造成問題。

借完了最好把值還回去,不然到時候出了問題別找我:-(。另外,也可以自己定義專用的全域變數供傳送用,雖然較麻煩,但是更不容易發生問題。

ExecuteFunc

  • 做法三:

    function AddTimeredEffectTargetUnit_Child takes nothing returns nothing
        local effect timeredeffect = bj_lastCreatedEffect
        local real duration = bj_enumDestructableRadius
        call PolledWait( duration )
        call DestroyEffect( timeredeffect )
    endfunction
    
    function AddTimeredEffectTargetUnit takes string modelName, unit whichUnit, 下行接續
        string whichAttach, real duration returns nothing
        local real Bj_enumDestructableRadius = bj_enumDestructableRadius
        set bj_lastCreatedEffect = AddSpecialEffectTarget(modelName, whichUnit, whichAttach )
        set bj_enumDestructableRadius = duration
        call ExecuteFunc("AddTimeredEffectTargetUnit_Child")
        set bj_enumDestructableRadius = Bj_enumDestructableRadius
    endfunction
    

     

其中的奧秘就在於這個神奇的函數:

    call ExecuteFunc("函數名") 

這個函數會自動搜尋指定的函數名稱,並且新增一道執行緒去執行它。

更神的是,它可以打破JASS的「向前參照」原則,執行寫在後面的函數!例如:

function Test takes nothing returns nothing
    call MyFunc()
endfunction

function MyFunc takes nothing returns nothing
    call BJDebugMsg("MyFunc is called")
endfunction

由於你不能呼叫此函數以後的函數,這樣寫會出錯。

然而,這樣寫就沒有問題了:

function Test takes nothing returns nothing
    call ExecuteFunc("MyFunc")
endfunction

function MyFunc takes nothing returns nothing
    call BJDebugMsg("MyFunc is executed")
endfunction

據說使用ExecuteFunc時,如果被調用函數的位置和調用函數的那行差距太遠,會導致調用失敗。所以請盡寫靠近一點。

就其它小問題而言,這個函數對於偵錯有些不利,假設現在你有個函數叫做Mary,這個函數的呼叫用法是call ExecuteFunc("Mary"),如果以後你把Mary改名為George呢?由於"Mary"仍然是合法的字串,編譯器不會報錯,不過事實上這個函數已經不存在了, 執行時便會出問題。如果使用前面的ExecuteCode就沒有這個缺點,如果你忘了改掉裡面的function Mary,編譯器將會毫不客氣地報錯,並且告訴你哪幾行要改。

此外這個函數對於有些地圖加密程式造成困擾,因為相當多的加密程式都會更動函數和變數名稱為不規則字串,以使有心人難以破解,同時壓縮檔案大小。可是字串一但被變動,ExecuteFunc就會造成問題,目前大多加密程式的設計者都有考慮到此問題而另行開發解決之道,不過偶爾因此會產生其它的小問題(例如地圖的某些字串走樣)。有些加密程式會附帶一些選項,如果你的地圖有用ExecuteFunc就勾(或就不勾)之類的,都是為了處理這類問題。有鑑於此,讀者若覺得使用ExecuteFunc不保險,還是可以 考慮前面的ExecuteCode,畢竟「向後參照」的功能並沒有那麼常用。

  • 使用做法三的函數,我現在如果寫:

        call AddTimeredEffectTargetUnit( "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl", 下行接續
            Unit1, "overhead", 10.0)
        call AddTimeredEffectTargetUnit( "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl", 下行接續
            Unit2, "overhead", 15.0)
        call PolledWait(2.0)
        call AddTimeredEffectTargetUnit( "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl", 下行接續
            Unit3, "overhead", 10.0)
        call PolledWait(4.0)
        call AddTimeredEffectTargetUnit( "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl", 下行接續
            Unit4, "overhead", 10.0)
        ...... 
    

     

每個AddTimeredEffectTargetUnit都會自己建立一個執行緒,並且自動在指定的時間到達後把創造的特效刪除掉,不同的執行緒之間完全不會互相影響。

這就是多執行緒的寫法,如此一來,我們的程式碼就能寫得非常精簡漂亮。想想看,如果不使用這個函數,要如何達到上面的效果,光是寫區域變數和計算每一段間距的等待的時間,絕對足以把你搞得頭暈腦脹!

多執行緒並不是JASS特有的,反倒是GUI Trigger更多,每一個觸發器自從事件啟動後就創造了一個執行緒,直到觸發執行結束。而每一次的Run Trigger也會建立一個新的執行緒,反倒JASS呼叫函數多只是處於同一 執行緒。

當然,多執行緒也是有缺點的,它們通常比單執行緒的寫法耗資源。以這個函數為例,每建立一個執行緒,就建立了一個PolledWait系統,而大家都知道PolledWait是藉由創造計時器和不斷重複地迴圈來逼近一個正確的等待時間;相對地,如果採用單執行緒,同一時間只要進行一個PolledWait就好,不過代價是,寫程式碼會寫得非常辛苦!

想要了解更多有關多執行緒的概念,讀者可參考多執行緒範例,傭兵護主

綜合教學/jass入門教學/a5.多執行緒.txt · 上一次變更: 2007年11月12日 4:47 am 來自 wasabi
www.chimeric.de Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0