Creazione di istanze dell'app con l'API ciclo di vita dell'app
Il modello di creazione di istanze dell'app determina se più istanze del processo dell'app possono essere eseguite contemporaneamente. L'API del ciclo di vita dell'app nella SDK per app di Windows consente di controllare il numero di istanze dell'app che possono essere eseguite contemporaneamente e di reindirizzare le attivazioni ad altre istanze quando necessario.
Questo articolo descrive come usare l'API del ciclo di vita dell'app per controllare la creazione di istanze delle app nelle app WinUI.
Prerequisiti
Per usare l'API del ciclo di vita dell'app nelle app WinUI 3:
- Scaricare e installare la versione più recente di Windows App SDK. Per altre informazioni, vedere Introduzione a WinUI.
- Seguire le istruzioni per creare il primo progetto WinUI 3 o per usare SDK per app di Windows in un progetto esistente.
App istanza singola
Le app sono a istanza singola se è possibile eseguire un solo processo principale alla volta. Il tentativo di avviare una seconda istanza in un'app a istanza singola comporta in genere l'attivazione della finestra principale della prima istanza. Si noti che questo vale solo per il processo principale. Le app a istanza singola possono creare più processi in background e comunque essere considerate a istanza singola.
Le app WinUI sono multiistanza per impostazione predefinita, ma hanno la possibilità di diventare a istanza singola decidendo in fase di avvio se creare una nuova istanza o attivare un'istanza esistente.
L'app Microsoft Foto è un buon esempio di un'app WinUI a istanza singola. Quando avvii Foto per la prima volta, verrà creata una nuova finestra. Se tenti di avviare di nuovo Foto, la finestra esistente verrà attivata.
Per un esempio di come implementare una singola istanza in un'app WinUI 3 con C#, vedi Creare un'app WinUI a istanza singola.
App a istanza multipla
Le app sono a istanza multipla se il processo principale può essere eseguito più volte contemporaneamente. Il tentativo di avviare una seconda istanza di un'app a istanze multipla crea un nuovo processo e una finestra principale.
Tradizionalmente, le app senza pacchetti sono multiistanza per impostazione predefinita, ma possono implementare la creazione di istanze singole quando necessario. In genere, questa operazione viene eseguita usando un singolo mutex denominato per indicare se un'app è già in esecuzione.
Blocco note è un buon esempio di app a istanza multipla. Ogni volta che si tenta di avviare Blocco note, viene creata una nuova istanza di Blocco note indipendentemente dal numero di istanze già in esecuzione.
Differenze tra la creazione di istanze in SDK per app di Windows e la creazione di istanze di UWP
Il comportamento di creazione di istanze in SDK per app di Windows si basa su modello e classe di UWP, ma con alcune differenze fondamentali:
Classe AppInstance
- UWP: la classe Windows.ApplicationModel.AppInstance è incentrata esclusivamente sugli scenari di reindirizzamento dell'istanza.
- SDK per app di Windows: la classe Microsoft.Windows.AppLifeycle.AppInstance supporta scenari di reindirizzamento dell'istanza e contiene funzionalità aggiuntive per supportare nuove funzionalità nelle versioni successive.
Elenco di istanze
- UWP: GetInstances restituisce solo le istanze registrate esplicitamente dall'app per il potenziale reindirizzamento.
- SDK per app di Windows: GetInstances restituisce tutte le istanze in esecuzione dell'app che usano l'API AppInstance, indipendentemente dal fatto che abbiano registrato o meno una chiave. Ciò può includere l'istanza corrente. Se si desidera che l'istanza corrente venga inclusa nell'elenco, chiamare
AppInstance.GetCurrent
. Elenchi separati vengono mantenuti per versioni diverse della stessa app, nonché per istanze di app avviate da utenti diversi.
Registrazione di chiavi
Ogni istanza di un'app a istanza multipla può registrare una chiave arbitraria tramite il metodo FindOrRegisterForKey
. Le chiavi non hanno alcun significato intrinseco; le app possono usare chiavi in qualsiasi forma o modalità desiderata.
Un'istanza di un'app può impostare la chiave in qualsiasi momento, ma per ogni istanza è consentita una sola chiave; l'impostazione di un nuovo valore sovrascrive il valore precedente.
Un'istanza di un'app non può impostare la sua chiave allo stesso valore già registrato da un'altra istanza. Se si tenta di registrare una chiave esistente, FindOrRegisterForKey
viene restituita l'istanza dell'app che ha già registrato detta chiave.
- UWP: un'istanza deve registrare una chiave per essere inclusa nell'elenco restituito da GetInstances.
- SDK per app di Windows: la registrazione di una chiave viene disassociata dall'elenco di istanze. Un'istanza non deve registrare una chiave per essere inclusa nell'elenco.
Annullamento della registrazione delle chiavi
Un'istanza di un'app può annullare la registrazione della relativa chiave.
- UWP: quando un'istanza annulla la registrazione della chiave, quest'ultima non è più disponibile per il reindirizzamento dell'attivazione e non è inclusa nell'elenco delle istanze restituite da GetInstances.
- SDK per app di Windows: un'istanza che ha annullata la registrazione della chiave è ancora disponibile per il reindirizzamento dell'attivazione ed è ancora inclusa nell'elenco delle istanze restituite da GetInstances.
Destinazioni di reindirizzamento dell'istanza
Più istanze di un'app possono attivarsi tra loro, un processo denominato "reindirizzamento dell'attivazione". Ad esempio, un'app potrebbe implementare una singola istanza inizializzandosi solo se non vengono trovate altre istanze dell'app all'avvio e invece reindirizzarsi e uscire se esiste un'altra istanza. Le app a istanza multipla possono reindirizzare le attivazioni, quando appropriato, in base alla logica di business dell'app. Quando un'attivazione viene reindirizzata a un'altra istanza, utilizza il callback Activated
dell'istanza, lo stesso callback usato in tutti gli altri scenari di attivazione.
- UWP: solo le istanze che hanno registrato una chiave possono essere una destinazione per il reindirizzamento.
- SDK per app di Windows: qualsiasi istanza può essere una destinazione di reindirizzamento, indipendentemente dal fatto che abbia o meno una chiave registrata.
Comportamento post-reindirizzamento
UWP: il reindirizzamento è un'operazione di terminazione; l'app termina dopo il reindirizzamento dell'attivazione, anche se il reindirizzamento non è riuscito.
SDK per app di Windows: in SDK per app di Windows il reindirizzamento non è un'operazione di terminazione. Questo in parte riflette i potenziali problemi nella terminazione arbitraria di un'app Win32 che potrebbe aver già allocato memoria, ma consente anche il supporto di scenari di reindirizzamento più sofisticati. Si consideri un'app a istanza multipla in cui un'istanza riceve una richiesta di attivazione durante l'esecuzione di una grande quantità di lavoro a elevato utilizzo della CPU. Tale app può reindirizzare la richiesta di attivazione a un'altra istanza e continuare l'elaborazione. Questo scenario non sarebbe possibile se l'app è terminata dopo il reindirizzamento.
Una richiesta di attivazione può essere reindirizzata più volte. L'istanza A potrebbe reindirizzare all'istanza B, che a sua volta potrebbe reindirizzare all'istanza C. App SDK per app di Windows che sfruttano questa funzionalità devono proteggersi dal reindirizzamento circolare. Se, nell'esempio precedente, C reindirizza ad A, si è in presenza di un potenziale ciclo di attivazione infinito. Spetta all'app determinare come gestire il reindirizzamento circolare a seconda dei flussi di lavoro supportati dall'app stessa.
Eventi di attivazione
Per gestire la riattivazione, l'app può registrarsi per un evento attivato.
- UWP: l'evento passa un IActivatedEventArgs all'app.
- SDK per app di Windows: l'evento passa un'istanza di Microsoft.Windows.AppLifecycle.AppActivationArguments all'app, che contiene una delle istanze
-ActivatedEventArgs
.
Esempi
Gestione delle attivazioni
Questo esempio illustra come un'app viene registrata per e gestisce un evento Activated
. Quando riceve un evento Activated
, questa app usa gli argomenti dell'evento per determinare quale tipo di azione ha causato l'attivazione e risponde in modo appropriato.
int APIENTRY wWinMain(
_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Initialize the Windows App SDK framework package for unpackaged apps.
HRESULT hr{ MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion) };
if (FAILED(hr))
{
OutputFormattedDebugString(
L"Error 0x%X in MddBootstrapInitialize(0x%08X, %s, %hu.%hu.%hu.%hu)\n",
hr, majorMinorVersion, versionTag,
minVersion.Major, minVersion.Minor, minVersion.Build, minVersion.Revision);
return hr;
}
if (DecideRedirection())
{
return 1;
}
// Connect the Activated event, to allow for this instance of the app
// getting reactivated as a result of multi-instance redirection.
AppInstance thisInstance = AppInstance::GetCurrent();
auto activationToken = thisInstance.Activated(
auto_revoke, [&thisInstance](
const auto& sender, const AppActivationArguments& args)
{ OnActivated(sender, args); }
);
// Carry on with regular Windows initialization.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_CLASSNAME, szWindowClass, MAX_LOADSTRING);
RegisterWindowClass(hInstance);
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
MddBootstrapShutdown();
return (int)msg.wParam;
}
void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
int const arraysize = 4096;
WCHAR szTmp[arraysize];
size_t cbTmp = arraysize * sizeof(WCHAR);
StringCbPrintf(szTmp, cbTmp, L"OnActivated (%d)", activationCount++);
ExtendedActivationKind kind = args.Kind();
if (kind == ExtendedActivationKind::Launch)
{
ReportLaunchArgs(szTmp, args);
}
else if (kind == ExtendedActivationKind::File)
{
ReportFileArgs(szTmp, args);
}
}
Logica di reindirizzamento basata sul tipo di attivazione
In questo esempio, l'app registra un gestore per l'evento Attivato e controlla anche la presenza degli argomenti dell'evento di attivazione per decidere se reindirizzare l'attivazione a un'altra istanza.
Per la maggior parte dei tipi di attivazione, l'app continua con il suo processo di inizializzazione normale. Tuttavia, se l'attivazione è stata causata dall'apertura di un tipo di file associato e se un'altra istanza di questa app ha già aperto il file, l'istanza corrente reindirizza l'attivazione all'istanza esistente e si chiude.
Questa app usa la registrazione della chiave per determinare quali file sono aperti in quali istanze. Quando un'istanza apre un file, registra una chiave che include tale nome del file. Altre istanze possono quindi esaminare le chiavi registrate e cercare nomi di file specifici e registrarsi come istanza del file, se non è già presente un'altra istanza.
Si noti che, anche se la registrazione della chiave stessa fa parte dell'API del ciclo di vita dell'app in SDK per app di Windows, il contenuto della chiave viene specificato solo all'interno dell'app stessa. Un'app non deve registrare un nome del file o altri dati significativi. Questa app, tuttavia, ha deciso di tenere traccia dei file aperti tramite chiavi, in base alle esigenze specifiche e ai flussi di lavoro supportati.
bool DecideRedirection()
{
// Get the current executable filesystem path, so we can
// use it later in registering for activation kinds.
GetModuleFileName(NULL, szExePath, MAX_PATH);
wcscpy_s(szExePathAndIconIndex, szExePath);
wcscat_s(szExePathAndIconIndex, L",1");
// Find out what kind of activation this is.
AppActivationArguments args = AppInstance::GetCurrent().GetActivatedEventArgs();
ExtendedActivationKind kind = args.Kind();
if (kind == ExtendedActivationKind::Launch)
{
ReportLaunchArgs(L"WinMain", args);
}
else if (kind == ExtendedActivationKind::File)
{
ReportFileArgs(L"WinMain", args);
try
{
// This is a file activation: here we'll get the file information,
// and register the file name as our instance key.
IFileActivatedEventArgs fileArgs = args.Data().as<IFileActivatedEventArgs>();
if (fileArgs != NULL)
{
IStorageItem file = fileArgs.Files().GetAt(0);
AppInstance keyInstance = AppInstance::FindOrRegisterForKey(file.Name());
OutputFormattedMessage(
L"Registered key = %ls", keyInstance.Key().c_str());
// If we successfully registered the file name, we must be the
// only instance running that was activated for this file.
if (keyInstance.IsCurrent())
{
// Report successful file name key registration.
OutputFormattedMessage(
L"IsCurrent=true; registered this instance for %ls",
file.Name().c_str());
}
else
{
keyInstance.RedirectActivationToAsync(args).get();
return true;
}
}
}
catch (...)
{
OutputErrorString(L"Error getting instance information");
}
}
return false;
}
Reindirizzamento arbitrario
Questo esempio si espande nell'esempio precedente aggiungendo regole di reindirizzamento più sofisticate. L'app esegue comunque il controllo del file aperto dall'esempio precedente. Tuttavia, quando l'esempio precedente crea sempre una nuova istanza, se non viene reindirizzato in base al controllo del file aperto, in questo esempio viene aggiunto il concetto di istanza "riutilizzabile". Se viene trovata un'istanza riutilizzabile, l'istanza corrente reindirizza all'istanza riutilizzabile ed esce. In caso contrario, si registra come riutilizzabile e continua la normale inizializzazione.
Anche in questo caso, si noti che il concetto di istanza "riutilizzabile" non esiste nell'API ciclo di vita dell'app; questo viene creato e usato solo all'interno dell'app stessa.
int APIENTRY wWinMain(
_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
// Initialize COM.
winrt::init_apartment();
AppActivationArguments activationArgs =
AppInstance::GetCurrent().GetActivatedEventArgs();
// Check for any specific activation kind we care about.
ExtendedActivationKind kind = activationArgs.Kind;
if (kind == ExtendedActivationKind::File)
{
// etc... as in previous scenario.
}
else
{
// For other activation kinds, we'll trawl all instances to see if
// any are suitable for redirecting this request. First, get a list
// of all running instances of this app.
auto instances = AppInstance::GetInstances();
// In the simple case, we'll redirect to any other instance.
AppInstance instance = instances.GetAt(0);
// If the app re-registers re-usable instances, we can filter for these instead.
// In this example, the app uses the string "REUSABLE" to indicate to itself
// that it can redirect to a particular instance.
bool isFound = false;
for (AppInstance instance : instances)
{
if (instance.Key == L"REUSABLE")
{
isFound = true;
instance.RedirectActivationToAsync(activationArgs).get();
break;
}
}
if (!isFound)
{
// We'll register this as a reusable instance, and then
// go ahead and do normal initialization.
winrt::hstring szKey = L"REUSABLE";
AppInstance::FindOrRegisterForKey(szKey);
RegisterClassAndStartMessagePump(hInstance, nCmdShow);
}
}
return 1;
}
Orchestrazione del reindirizzamento
Questo esempio aggiunge di nuovo un comportamento di reindirizzamento più sofisticato. In questo caso, un'istanza dell'app può registrarsi come istanza che gestisce tutte le attivazioni di un tipo specifico. Quando un'istanza di un'app riceve un'attivazione Protocol
, verifica innanzitutto la presenza di un'istanza già registrata per gestire le attivazioni Protocol
. Se ne trova una, reindirizza l'attivazione a tale istanza. In caso contrario, l'istanza corrente si registra per le attivazioni Protocol
e quindi applica logica aggiuntiva (non visualizzata) che potrebbe reindirizzare l'attivazione per altri motivi.
void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
const ExtendedActivationKind kind = args.Kind;
// For example, we might want to redirect protocol activations.
if (kind == ExtendedActivationKind::Protocol)
{
auto protocolArgs = args.Data().as<ProtocolActivatedEventArgs>();
Uri uri = protocolArgs.Uri();
// We'll try to find the instance that handles protocol activations.
// If there isn't one, then this instance will take over that duty.
auto instance = AppInstance::FindOrRegisterForKey(uri.AbsoluteUri());
if (!instance.IsCurrent)
{
instance.RedirectActivationToAsync(args).get();
}
else
{
DoSomethingWithProtocolArgs(uri);
}
}
else
{
// In this example, this instance of the app handles all other
// activation kinds.
DoSomethingWithNewActivationArgs(args);
}
}
A differenza della versione UWP di RedirectActivationTo
, l'implementazione di SDK per app di Windows di RedirectActivationToAsync richiede il passaggio esplicito di argomenti evento durante il reindirizzamento delle attivazioni. Ciò è necessario perché la piattaforma UWP controlla rigorosamente le attivazioni e può garantire che gli argomenti di attivazione corretti vengano passati alle istanze corrette, la versione di SDK per app di Windows supporta molte piattaforme e non può basarsi su funzionalità specifiche della piattaforma UWP. Uno dei vantaggi di questo modello è che le app che usano SDK per app di Windows hanno la possibilità di modificare o sostituire gli argomenti che vengono passati all'istanza di destinazione.
Reindirizzamento senza blocco
La maggior parte delle app vuole reindirizzare il prima possibile, prima di eseguire operazioni di inizializzazione non necessarie. Per alcuni tipi di app, la logica di inizializzazione viene eseguita in un thread STA, che non deve essere bloccato. Il metodo AppInstance.RedirectActivationToAsync è asincrono e l'app chiamante deve attendere il completamento del metodo; in caso contrario, il reindirizzamento avrà esito negativo. Tuttavia, l'attesa su una chiamata asincrona blocca la STA. In queste situazioni, richiamare RedirectActivationToAsync in un altro thread e impostare un evento al termine del richiamo. Attendere quindi l'evento usando API non bloccanti, ad esempio CoWaitForMultipleObjects. Ecco un esempio C# per un'app WPF.
private static bool DecideRedirection()
{
bool isRedirect = false;
// Find out what kind of activation this is.
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
ExtendedActivationKind kind = args.Kind;
if (kind == ExtendedActivationKind.File)
{
try
{
// This is a file activation: here we'll get the file information,
// and register the file name as our instance key.
if (args.Data is IFileActivatedEventArgs fileArgs)
{
IStorageItem file = fileArgs.Files[0];
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(file.Name);
// If we successfully registered the file name, we must be the
// only instance running that was activated for this file.
if (keyInstance.IsCurrent)
{
// Hook up the Activated event, to allow for this instance of the app
// getting reactivated as a result of multi-instance redirection.
keyInstance.Activated += OnActivated;
}
else
{
isRedirect = true;
// Ensure we don't block the STA, by doing the redirect operation
// in another thread, and using an event to signal when it has completed.
redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
if (redirectEventHandle != IntPtr.Zero)
{
Task.Run(() =>
{
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(redirectEventHandle);
});
uint CWMO_DEFAULT = 0;
uint INFINITE = 0xFFFFFFFF;
_ = CoWaitForMultipleObjects(
CWMO_DEFAULT, INFINITE, 1,
new IntPtr[] { redirectEventHandle }, out uint handleIndex);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error getting instance information: {ex.Message}");
}
}
return isRedirect;
}
Annullare la registrazione per il reindirizzamento
Le app che hanno registrato una chiave possono annullare la registrazione della chiave in qualsiasi momento. In questo esempio si presuppone che l'istanza corrente avesse registrato in precedenza una chiave che indica la presenza di un file specifico aperto, ovvero i successivi tentativi di aprire tale file verrebbero reindirizzati. Quando il file viene chiuso, la chiave che contiene il nome del file deve essere eliminata.
void CALLBACK OnFileClosed(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
AppInstance::GetCurrent().UnregisterKey();
}
Avviso
Anche se le registrazioni delle chiavi vengono annullate automaticamente quando termina il processo, è possibile che siano possibili race condition in cui un'altra istanza può aver avviato un reindirizzamento all'istanza terminata prima che l'istanza terminata non sia stata registrata. Per attenuare questa possibilità, un'app può usare UnregisterKey per annullare manualmente la registrazione della chiave prima che venga terminata, dando all'app la possibilità di reindirizzare le attivazioni a un'altra app che non è in fase di uscita.
Informazioni sull'istanza
La classe Microsoft.Windows.AppLifeycle.AppInstance rappresenta una singola istanza di un'app. Nell'anteprima corrente AppInstance
sono inclusi solo i metodi e le proprietà necessari per supportare il reindirizzamento dell'attivazione. Nelle versioni successive, AppInstance
si espande per includere altri metodi e proprietà rilevanti per un'istanza dell'app.
void DumpExistingInstances()
{
for (AppInstance const& instance : AppInstance::GetInstances())
{
std::wostringstream sStream;
sStream << L"Instance: ProcessId = " << instance.ProcessId
<< L", Key = " << instance.Key().c_str() << std::endl;
::OutputDebugString(sStream.str().c_str());
}
}