2014年5月4日 星期日

跨 Process 的 Mutex 使用

撰寫多執行序的軟體時,Mutex 是一個很常使用來保護資料同步性的工具,根據 AbandonedMutexException 的 MSDN 說明,若 Mutex 在某個 Thread 中被 Lock 而卻在另一個 Thread 被 Release 即會發生這種例外,這個錯誤在 Windows Phone 上如果撰寫跨 Process 的流程時其實很容易被製造出來。

在 Windows Phone 7.1、8.0 的框架下,應用程式想在背景進行某些邏輯動作時只有撰寫 BackgroundAgent 一途,而 BackgroundAgent 不論是 AudioPlayerAgent 還是 ScheduledTaskAgent 等 Class 都有個特性,它們和應用程式本身並不屬於同一個 Process 區間,所以像 Singleton 等這類用來管理單一物件的手法是沒辦法使用的,會造成 Application 本身的 Process 有一個 singleton object、Agent 本身也有一個 singleton object。

因為這種狀況,在控管資源如檔案、設定值、資料表的同步問題就必須用到像 Mutex 這類可跨 Process 的元件,以我們專案中 AudioPlayerAgent 的這段程式碼為例

public void LogEvent(String eventId)
{
    SqliteConnection DBConn = null;

    try
    {
        AppService.Instance.mDBMutex.WaitOne();
        try
        {
            DBConn = new SqliteConnection(Constants.SQLITE_DB_PATH);
            if (DBConn != null)
            {
                DBConn.Open();
                // LogEvent(eventId, DBConn);
            }
        }
        catch (Exception e)
        {
            KKLogger.Instance.LogE(e);
        }
    }
    finally
    {
        if (DBConn != null && DBConn.State == System.Data.ConnectionState.Open)
        {
            DBConn.Close();
        }
        AppService.Instance.mDBMutex.ReleaseMutex();
    }
}

如果 Agent 正在執行這個 function 的途中,應用程式呼叫了 BackgroundAudioPlayer.Instance.Close(); 這類會釋放 Agent 資源的方法時,Agent 是會被立即 Release 的,並不會等待執行中的 Thread 結束,也就是上面這個 function 的 finally 不會被執行到的機率其實不低,也就發生 mDBMutex 沒被 Release 的情況。

在這種情況下,接下來所有的 mDBMutex 操作都會失敗,又由於 Mutex 對於系統來說是全域的,所以如果在 Application 啟動時就去操作同一個 Mutex 即會造成應用程式連開都開不起來,必須重新開機才有效。

所以這段看似嚴格包了很多層保護機制的寫法也是不夠的,在應用程式要將 Agent 釋放時,理想的方法是等待 Agent 的 Thread 執行結束時再釋放,比較容易的方法是使用旗標,讓 Agent 在確定釋放 Mutex 後主動檢查是否有人呼叫 Close 方法,由 Agent 自已在適當的時機關閉自己。


沒有留言:

張貼留言