Dela via


Instansiering av appar med livscykel-API för appar

En apps instancing-modell avgör om flera instanser av appens process kan köras samtidigt. API:et för appens livscykel i Windows App SDK är ett sätt att styra hur många instanser av din app som kan köras samtidigt och att omdirigera aktiveringar till andra instanser när det behövs.

Den här artikeln beskriver hur du använder API:et för appens livscykel för att styra appinstancing i dina WinUI-appar.

Förutsättningar

Så här använder du API:et för appens livscykel i WinUI 3-appar:

Appar med en enda instans

Appar är en instans om det bara kan finnas en huvudprocess som körs i taget. Försök att starta en andra instans av en app med en instans resulterar vanligtvis i att den första instansens huvudfönster aktiveras i stället. Observera att detta endast gäller huvudprocessen. Appar med en instans kan skapa flera bakgrundsprocesser och betraktas fortfarande som enskilda instanser.

WinUI-appar är som standard flera instanser, men kan bli enkla instanser genom att vid start bestämma om en ny instans ska skapas eller en befintlig instans aktiveras i stället.

Microsoft Photos-appen är ett bra exempel på en WinUI-app med en instans. När du startar Foton för första gången skapas ett nytt fönster. Om du försöker starta Foton igen kommer det befintliga fönstret att aktiveras istället.

Ett exempel på hur du implementerar enkel instancing i en WinUI 3-app med C# finns i Skapa en WinUI-app med en instans.

Appar med flera instanser

Appar är multiinstanser om huvudprocessen kan köras flera gånger samtidigt. Om du försöker starta en andra instans av en app med flera instanser skapas en ny process och ett nytt huvudfönster.

Traditionellt sett är uppackade appar flera instanser som standard, men kan implementera enkel instancing vid behov. Detta görs vanligtvis med hjälp av en enda namngiven mutex för att indikera om en app redan körs.

Anteckningsblock är ett bra exempel på en app som kan köras i flera instanser. Varje gång du försöker starta Anteckningar skapas en ny instans av Anteckningar oavsett hur många instanser som redan körs.

Hur instansering av Windows App SDK skiljer sig från UWP-instansiering

Instancing-beteendet i SDK för Windows-appen baseras på UWP:s modell, klass, men med några viktiga skillnader:

Klassen AppInstance

Lista över instanser

  • UWP: GetInstances returnerar endast de instanser som appen uttryckligen registrerade för möjlig omdirigering.
  • SDK för Windows-app: GetInstances returnerar alla instanser av appen som använder AppInstance-API:et, oavsett om de har registrerat en nyckel eller inte. Detta kan inkludera den aktuella instansen. Om du vill att den aktuella instansen ska finnas med i listan anropar AppInstance.GetCurrentdu . Separata listor upprätthålls för olika versioner av samma app, liksom instanser av appar som startas av olika användare.

Registrera nycklar

Varje instans av en app med flera instanser kan registrera en godtycklig nyckel via FindOrRegisterForKey metoden. Nycklar har ingen inneboende mening; Appar kan använda nycklar i vilken form eller på vilket sätt de vill.

En instans av en app kan ange sin nyckel när som helst, men endast en nyckel tillåts för varje instans. Om du anger ett nytt värde skrivs det tidigare värdet över.

En instans av en app kan inte ange sin nyckel till samma värde som en annan instans redan har registrerat. Om du försöker registrera en befintlig nyckel returneras FindOrRegisterForKey den appinstans som redan har registrerat nyckeln.

  • UWP: En instans måste registrera en nyckel för att kunna ingå i listan som returneras från GetInstances.
  • SDK för Windows-appar: Registrering av en nyckel är frikopplad från listan över instanser. En instans behöver inte registrera en nyckel för att tas med i listan.

Avregistrera nycklar

En instans av en app kan avregistrera sin nyckel.

  • UWP: När en instans avregistrerar sin nyckel är den inte längre tillgänglig för omdirigering av aktivering och ingår inte i listan över instanser som returneras från GetInstances.
  • SDK för Windows-appar: En instans som har avregistrerat sin nyckel är fortfarande tillgänglig för omdirigering av aktivering och ingår fortfarande i listan över instanser som returneras från GetInstances.

Instansers omdirigeringsmål

Flera instanser av en app kan aktivera varandra, en process som kallas "aktiveringsomdirigering". En app kan till exempel implementera enkel instancing genom att bara initiera sig själv om inga andra instanser av appen hittas vid start, och i stället omdirigera och avsluta om det finns en annan instans. Appar med flera instanser kan omdirigera aktiveringar när det är lämpligt enligt appens affärslogik. När en aktivering omdirigeras till en annan instans använder den instansens Activated callback, samma callback som används i alla andra aktiveringsscenarier.

  • UWP: Endast instanser som har registrerat en nyckel kan vara ett mål för omdirigering.
  • SDK för Windows-appar: Alla instanser kan vara ett omdirigeringsmål, oavsett om de har en registrerad nyckel eller inte.

Beteende efter omdirigering

  • UWP: Omdirigering är en terminaloperation; Appen avslutas efter att aktiveringen har omdirigerats, även om omdirigeringen misslyckades.

  • SDK för Windows-appar: I DK FÖR Windows-appar är omdirigering inte en terminalåtgärd. Detta återspeglar delvis de potentiella problemen med att godtyckligt avsluta en Win32-app som redan kan ha allokerat en del minne, men ger också stöd för mer avancerade omdirigeringsscenarier. Överväg en app med flera instanser där en instans tar emot en aktiveringsbegäran samtidigt som den utför en stor mängd CPU-intensivt arbete. Appen kan omdirigera aktiveringsbegäran till en annan instans och fortsätta bearbetningen. Det scenariot skulle inte vara möjligt om appen avslutades efter omdirigering.

En aktiveringsbegäran kan omdirigeras flera gånger. Instans A kan omdirigeras till instans B, som i sin tur kan omdirigera till instans C. SDK-appar för Windows-appar som utnyttjar den här funktionen måste skydda sig mot cirkulär omdirigering – om C omdirigerar till A i exemplet ovan finns det en potentiell oändlig aktiveringsloop. Det är upp till appen att avgöra hur cirkulär omdirigering ska hanteras beroende på vad som är meningsfullt för de arbetsflöden som appen stöder.

Aktivering händelser

För att kunna hantera återaktivering kan appen registrera sig för en Aktiverad händelse.

Exempel

Hantera aktiveringar

Det här exemplet visar hur en app registrerar sig för och hanterar en Activated händelse. När den tar emot en händelse använder den här appen händelseargumenten Activated för att avgöra vilken typ av åtgärd som orsakade aktiveringen och svarar på lämpligt sätt.

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);
    }
}

Omdirigeringslogik baserad på aktiveringstyp

I det här exemplet registrerar appen en hanterare för händelsen Activated och söker även efter aktiveringshändelsens args för att avgöra om aktiveringen ska omdirigeras till en annan instans.

För de flesta typer av aktiveringar fortsätter appen med sin vanliga initieringsprocess. Men om aktiveringen orsakades av att en associerad filtyp öppnades, och om en annan instans av den här appen redan har filen öppen, kommer den aktuella instansen att omdirigera aktiveringen till den befintliga instansen och avsluta.

Den här appen använder nyckelregistrering för att avgöra vilka filer som är öppna i vilka instanser. När en instans öppnar en fil registrerar den en nyckel som innehåller filnamnet. Andra instanser kan sedan undersöka de registrerade nycklarna och leta efter specifika filnamn och registrera sig som den filens instans om ingen annan instans redan har gjort det.

Observera att även om själva nyckelregistreringen är en del av API:et för appens livscykel i SDK:erna för Windows-appar, anges innehållet i nyckeln endast i själva appen. En app behöver inte registrera ett filnamn eller andra meningsfulla data. Den här appen har dock bestämt sig för att spåra öppna filer via nycklar baserat på dess specifika behov och arbetsflöden som stöds.

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;
}

Godtycklig omdirigering

Det här exemplet utökar det föregående exemplet genom att lägga till mer avancerade omdirigeringsregler. Appen utför fortfarande kontrollen av öppen fil från föregående exempel. Men där det föregående exemplet alltid skulle skapa en ny instans om den inte omdirigerade baserat på den öppna filkontrollen, lägger det här exemplet till begreppet "återanvändbar" instans. Om en återanvändbar instans hittas omdirigeras den aktuella instansen till den återanvändbara instansen och avslutas. Annars registreras den som återanvändbar och fortsätter med sin normala initiering.

Observera återigen att begreppet "återanvändbar" instans inte finns i API:et för appens livscykel. Den skapas och används endast i själva appen.

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;
}

Omdirigeringsorkestrering

I det här exemplet läggs återigen till ett mer sofistikerat omdirigeringsbeteende. Här kan en appinstans registrera sig som den instans som hanterar alla aktiveringar av en viss typ. När en instans av en app tar emot en Protocol aktivering söker den först efter en instans som redan har registrerats för att hantera Protocol aktiveringar. Om den hittar en omdirigeras aktiveringen till den instansen. Annars registrerar sig den aktuella instansen för Protocol-aktiveringar och tillämpar sedan ytterligare logik (som inte visas) som kan omdirigera aktiveringen av någon annan orsak.

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);
    }
}

Till skillnad från UWP-versionen av RedirectActivationTokräver Windows App SDK:s implementering av RedirectActivationToAsync att händelseargument uttryckligen skickas vid omdirigering av aktiveringar. Detta är nödvändigt eftersom UWP noggrant kontrollerar aktiveringar och kan säkerställa att rätt aktiveringsargument skickas till rätt instanser, men Windows App SDK:s version stöder många plattformar och inte kan förlita sig på UWP-specifika funktioner. En fördel med den här modellen är att appar som använder Windows App SDK har möjlighet att ändra eller ersätta argumenten som ska skickas till målinstansen.

Omdirigering utan blockering

De flesta appar vill omdirigera så tidigt som möjligt innan de gör onödigt initieringsarbete. För vissa apptyper körs initieringslogik på en STA-tråd, som inte får blockeras. Metoden AppInstance.RedirectActivationToAsync är asynkron och den anropande appen måste vänta tills metoden har slutförts, annars misslyckas omdirigeringen. Om du väntar på ett asynkront anrop kommer dock STA att blockeras. I dessa situationer anropar du RedirectActivationToAsync i en annan tråd och anger en händelse när anropet är klart. Vänta sedan på händelsen med icke-blockerande API:er.

Följande är ett C#-exempel för en WPF-app:

private static bool DecideRedirection(string key)
{
    bool isRedirect = false;

    // Find out what kind of activation this is.
    AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind;

    if (kind == ExtendedActivationKind.Launch)
    {
        try
        {
            AppInstance keyInstance = AppInstance.FindOrRegisterForKey(key);

            // If we successfully registered the key, we must be the
            // only instance running that was activated for this key.
            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 += OnKeyInstanceActivated;
            }
            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.
                var redirectSemaphore = new Semaphore(0, 1);
                Task.Run(() =>
                {
                    keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
                    redirectSemaphore.Release();
                });
                redirectSemaphore.WaitOne();

            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Error getting instance information: {ex.Message}");
        }
    }

    return isRedirect;
}

Avregistrera för omdirigering

Appar som har registrerat en nyckel kan när som helst avregistrera nyckeln. Det här exemplet förutsätter att den aktuella instansen tidigare har registrerat en nyckel som anger att den har en specifik fil öppen, vilket innebär att efterföljande försök att öppna filen omdirigeras till den. När filen stängs måste nyckeln som innehåller filnamnet tas bort.

void CALLBACK OnFileClosed(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    AppInstance::GetCurrent().UnregisterKey();
}

Varning

Även om nycklar avregistreras automatiskt när deras process avslutas, är konkurrensförhållanden möjliga där en annan instans kan ha initierat en omdirigering till den avslutade instansen innan den avslutade instansen avregistrerades. För att minska den här risken kan en app använda UnregisterKey för att manuellt avregistrera sin nyckel innan den avslutas, vilket ger appen en chans att omdirigera aktiveringar till en annan app som inte håller på att avslutas.

Instansinformation

Klassen Microsoft.Windows.AppLifeycle.AppInstance representerar en enda instans av en app. I den aktuella förhandsversionen innehåller AppInstance endast de metoder och egenskaper som krävs för omdirigering av aktivering. I senare versioner kommer att utökas AppInstance för att inkludera andra metoder och egenskaper som är relevanta för en appinstans.

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());
    }
}

Skapa en WinUI-app med en instans

Microsoft.Windows.AppLifeycle.AppInstance