waveOutReset的N種死法, 及其解決方案

我遭遇到了調用waveOutReset死鎖的問題,在GOOGLE上一搜,遇到同樣問題的人還真不少,但沒有人很明確地找到造成DEADLOCK的原因,都是糊里糊塗就把問題解決了,然後把運行OK的代碼一貼完事。我花了四五個小時才徹底摸清楚規律,把這經驗拿出來共享

原則:
(1) waveOutReset不是立即返回的函數, 而需要等待駐留在WAVEDEV裏的音頻BUFFER全部標記爲WOM_DONE,經過callback proc處理完畢後, waveOutReset纔會返回.
(2) 在waveOutReset調用到返回之間的這段時間內, 禁止任何線程對處於reset中的HWAVEOUT調用任何WAVE API, 否則立刻造成死鎖.

把握住這兩個原則就可以根治waveOutReset死鎖問題了. 不過爲了把事情描述得更具體些, 我把網上多數人造成死鎖的情況列一下, 並給出解決方法

第一種死法:
在回調處理函數WaveCallbackFunc中,對標記爲WOM_DONE的BUFFER進行waveOutUnprepareHeader

錯誤分析:
調用waveOutReset後, 系統內所有未播放的buffer全部被標記爲WOM_DONE返回給WaveCallbackFunc, 這屬於waveOurReset處理過程的一部分, 此時waveOurReset還未返回. 所以這時如果調用到waveOutUnprepareHeader, 就會立刻造成死鎖. 我自己也是犯的這個錯誤。

解決方法:
在調用waveOurReset之前設置一個FLAG, 然後在WaveCallbackFunc裏要調用waveOutUnprepareHeader之前判斷一下這個FLAG, 如果處於reset過程中就跳過waveOutUnprepareHeader. 範例代碼:

static CRITICAL_SECTION g_CS;
static BOOL g_bResetting = FALSE;

void Reset()
{
        EnterCriticalSection(&g_CS);  //之前InitializeCriticalSection一下
        g_bResetting = TRUE;
        LeaveCriticalSection(&g_CS);
       
        waveOutReset();

        g_bResetting = FALSE;  //RESET後就不會調WaveCallbackFunc了,所以不需要鎖
}

VOID CALLBACK WaveCallbackFunc(HWAVEOUT hwo, UINT32 uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
       EnterCriticalSection(&g_CS);   //防止g_bResetting在if判斷之後, waveOutUnprepareHeader之前被設成1了, callback和reset處於不同線程
       
        if( (uMsg == WOM_DONE) && !g_bResetting )
        {
                waveOutUnprepareHeader(hWaveOut, pWaveHdr, sizeof(WAVEHDR));
        }
       
        LeaveCriticalSection(&g_CS);
}
       
另外, 如果使用了每個BUFFER定長的BUFFER-CHAIN來做生產者-消費者模型, 那麼在BUFFER-CHAIN初始化的時候順便把所有WAVEHDR都PREPARE好, 而只在BUFFER-CHAIN全部釋放的時候才做UNPREPARE, 那麼在每次waveOutWrite和WOM_DONE的循環裏就不需要PREPARE/UNPREPARE了,這也能避開上述情況造成的死鎖


第二種死法: 某些人喜歡在在回調處理函數WaveCallbackFunc中,調用waveOutWrite往系統裏填充新BUFFER.
錯誤分析: 同情況一, 在waveOutReset過程中,如果WaveCallbackFunc調用了waveOutWrite, 同樣會造成死鎖.
解決方法: 同情況一, 設置FLAG和臨界區配合來防止. 不過比較建議在另外的線程中調waveOutWrite寫入BUFFER, 這才符合生產-消費模型.

第三種死法: 在另外一個線程中調用waveOurWrite, 但是waveOutReset之前, 沒有停下那個線程
錯誤分析: RESET過程中,還有waveOurWrite調用
解決方法: 把waveOurWrite那個線程suspend或退出, 或者在waveOutWrite前面用FLAG+臨界區判斷

第四種死法: 也是最匪夷所思的死法, 從UI的某個按鈕一路調用到waveOutReset(處於同線程中),  而在WaveCallbackFunc中,同線程調用SetWindowText之類UI函數
錯誤分析:  事實是:
(1) 如果是在WindowProc的某個CASE裏調用SetWindowText, 調用者和SetWindowText處於同線程中,那麼運行正常, 多數UI也都會這麼寫;
(2) 如果處於Thread1的UI調用了waveOutReset, 導致處於Thread2中的WaveCallbackFunc運行並調用SetWindowText, 試圖設置處於Thread1的UIcallbackProc, 那麼SetWindowText會死鎖, 導致WaveCallbackFunc死掉, 最後表面上看起來就是waveOutReset死掉.其實我也不明白爲什麼這樣也能死,要問就去問蓋茨大叔吧.
解決方法:
在WaveCallbackFunc中,用RESET FLAG + 臨界區的方法保護UI調用函數, 使其在waveOutReset過程中不被調用, 或者用PostMessage這樣的異步函數,實在不得不同步影響UI的話就忍一忍等waveOurReset函數返回後再做吧.

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/renjwjx/archive/2009/03/03/3954012.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章