UWP - 介紹 multi-instance UWP app

Windows 10, version 1803 (10.0; Build 17134) 開始,UWP app 支援 multiple instances。

怎麽使用它呢?對於既有的 App 需要做那些調整?藉由這篇來介紹讓大家有些概念。

本篇參考 Create a multi-instance Universal Windows App 來介紹。 支援 multi-instance 的做法可透過安裝 Multi-Instance App Project Templates.Vsix 範本來得到兩種類型:

  • Multi-instance Redirection UWP App
    建立 multi-instance app 並帶有額外邏輯控制是否啓動新的 instance 或從已經啓動的 instances 選擇來使用; 例如:希望一次只有一個 instance 可以編輯相同檔案,當開啓檔案時則從已經開啓的 instances 來使用,不再建立 instance。
  • Multi-instance UWP App
    每次開啓 app 都是建立新 instance。例如:圖片瀏覽器就非常適合。

讓 App 支援 multi-instancing 幾個步驟:

  1. Package.appxmanifest 加入宣告
    在 namespace prefix 有新的 tag: desktop4iot2,代表只有 Desktop 與 IoT 設備才支援 multi-instancing。 加入 SupportsMultipleInstances,開發過 Background Task 應該知道 SupportsMultipleInstances 宣告,同樣地在 App Service 的宣告上也支援。參考如下:
    <?xml version="1.0" encoding="utf-8"?>
    <Package
      xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" 
      xmlns:iot2="http://schemas.microsoft.com/appx/manifest/iot/windows10/2" 
      IgnorableNamespaces="uap mp desktop4 iot2">
    
      <Applications>
        <Application Id="App"
          Executable="$targetnametoken$.exe"
          EntryPoint="multi_redirect_instance.App"
          desktop4:SupportsMultipleInstances="true"
          iot2:SupportsMultipleInstances="true">
        </Application>
      </Applications>
    </Package>
  2. 建立專用的 Program.cs 取代預設的處理機制
    預設 App 被啓動時會自動產生一份 App.g.i.cs,負責啓動前需要處理的事情,如下:
    #if !DISABLE_XAML_GENERATED_MAIN
    public static class Program
    {
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks","")]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        static void Main(string[] args)
        {
            global::Windows.UI.Xaml.Application.Start((p) => new App());
        }
    }
    #endif
    參考 Multi-instance activation redirection 介紹,改由自定義的 Program.cs 讓 App 啓動前做 multiple-instancing 的邏輯,如下: 1. 在專案檔(*.csproj) 的 DefineConstants 加入宣告:DISABLE_XAML_GENERATED_MAIN,讓啓動時使用自定義 Program.cs; 2. 加入如下的範例程式:
    public static class Program
    {
        static void Main(string[] args)
        {
            // 抓取啓動時給與的參數,例如:從 cmd 來的參數
            IActivatedEventArgs activatedArgs = AppInstance.GetActivatedEventArgs();
    
            // 判斷是否有系統推薦的 instance,有的話則直接使用,沒有再走自己的邏輯
            if (AppInstance.RecommendedInstance != null)
            {
                AppInstance.RecommendedInstance.RedirectActivationTo();
            }
            else
            {
                // 定義 key 為 instance 注冊,是常見的做法,可按照需求做調整
                // key 是唯一值,每次的 instance 都是新的
                // key 不是唯一值,instance 就可以重覆使用(redirect)
                uint number = CryptographicBuffer.GenerateRandomNumber();
                string key = (number % 2 == 0) ? "even" : "odd";
                var instance = AppInstance.FindOrRegisterInstanceForKey(key);
                if (instance.IsCurrentInstance)
                {
                    // 如果成功注冊,則代表為新的 instance
                    global::Windows.UI.Xaml.Application.Start((p) => new App());
                }
                else
                {
                    // 有其他的 instance 注冊相同的 key,則可以 redirect activation 到該 instance
                    instance.RedirectActivationTo();
                }
            }
        }
    }
    上面的範例是取得亂數指並取 2 的餘數,並給與兩種 key,因此最多只會有 2 個 instance 被建立。您可以調整為其他邏輯,例如:利用檔案完整路徑為 key,讓同一個檔案只有一個 instance 操作;或是利用功能切分等。如果您的目標是每次都開啓新的 instance 就不需要自定義 Program.cs。

兩個步驟就讓 UWP app 支援 multiple-instancing,下方繼續介紹幾個重要的元素。

[重要元素]

AppInstance class

系統會暫存該 app 已經開啓的 instances。

當 app process 在 Main method 被建立時,能利用 AppInstance 選擇是否繼續啓動該 instance 或是導向已經存在的 instance。

另外,shell 能提供推薦的 instance (RecommendedInstance),鼓勵我們選擇是否導向該 instance。

下面兩點要注意:

  1. AppInstance 目的只在 Main method 中使用,如果到其他地方使用有可能拿到的屬性是 null 或是使用方法是 failed;
  2. 任何 instances 被回傳之前,必須利用 FindOrRegisterInstanceForKey 來注冊;
  3. AppInstance 只能在有宣告 SupportsMultipleInstances 的專案,詳細説明:Package manifest schema reference for Windows 10
type name description
Properties IsCurrentInstance app 當前的 instance 是否為已注冊特定 key 的 instance。
  Key 當前 instance 的 key。
  RecommendedInstance shell 建議應用程式啟動的應用程式的實例被重定向。
Methods FindOrRegisterInstanceForKey(String) 搭配 key 注冊 instance 到系統或是找到已注冊該 key 的 instance。
  GetActivatedEventArgs() 取得當前的 IActivatedEventArgs,如同在 OnActivated 事件收到的内容。
  GetInstances() 取得當前 app 已經注冊的 instances。
  RedirectActivationTo() 重新導向 activation 到另一個特定的 instance。
  Unregister() 更新系統的暫存該 instance。

Background tasks 與 multi-instancing

  • Out-of-proc background tasks(跨處理序背景工作)支援 multi-instancing。一般而言,每個 new trigger results 都會產生 background task 的新 instance。雖然從技術面來説多個 background tasks 會在相同的 host process 被處理,但是系統還是會建立多個 instances。
  • In-proc background tasks 不支援 multi-instancing。
  • Background audio tasks 不支援 multi-instancing。
  • 當 app 注冊 background task 時,通常先檢查該工作是否已經注冊,然後刪除已經存在再重新注冊,或是不執行任何動作。這是一般的 multi-instancing apps 的處理方式。但 multi-instancing app 可能跟著 instance 注冊不同的 background task name,這樣一來,將造成相同的 trigger 有多個注冊,而且 multiple background task instance 將同時被啓動。
  • App-service 為每個連綫建的 background task 建立不同的 instance。讓 multi-instance apps 可以擁有自己的 app-service background task。更多介紹可以參考 UWP - 介紹 App Service 與新功能

Additional considerations

  • Multi-instancing 支援 UWP apps 運行在 desktop 與 Internet of Things(IoT)。
  • 為避免資源互相爭用問題,multi-instancing apps 必須有機制來分割/同步設定,操作 app-local storage 與其他資源。有些標準的做法,例如: mutexes, semaphores, events ... 等。
  • 如果 Package.appxmanifest 有宣告 SupportsMultipleInstances,它的 extensions 不需要再宣告一次。
  • 除了 background task 或 app service 外,在其他 extensions 宣告了 SupportsMultipleInstances,但主專案沒有宣告則會造成結構描述錯誤。
  • App 可利用 ResourceGroup 在 manifest 宣告把 multiple backgorund tasks 分組到同一個主機中。但是這與 multi-instancing 會有衝突,因爲每一個被啓動應該分開的 host,因此,app 不能在他們的 manifest 宣告 SupportsMultipleInstancesResourceGroup

[注意]

  • Multi-instancing 雖然支援 JavaScript applications,但不支援 multi-instancing redirection。因此,AppInstance class 無法使用在此類型的 applications。
  • 如果想讓 instances 互相溝通的話,也許可以建立 App Service 讓 main instance 來負責接受與處理,可以參考: lprichar/UwpMessageRelay
  • Mutex Class 專門用來同步存取被保護的資源(例如:檔案),因爲不同 instance 存取時會有互相衝突的問題,藉由呼叫 Mutex.WaitOne() 鎖住 threads 的存取,等到呼叫 Mutex.ReleaseMutex() 再開放給其他 threads 使用。
  • 目前 Store 不支援 IoT 裝置下載的 app,所以宣告 iot2:SupportsMultipleInstances 只適用與 Appx 獨立安裝。

======

multiple instances 我覺得最困難的問題在於支援可能互相爭奪,例如:SQLite 的使用就需要特別小心,最簡單就是使用 mutexes。

不過如果過去您開發過 Windows Phone 7.1 的音樂播放機制,對於 two process 的處理一定會非常有感覺。

因爲我自己開發的 App 還沒有需要到讓 app 支援 multiple instances,所以這篇寫的有點簡單。希望對大家還是有些幫助。

References