Muster zum asynchronen Programmieren

Muster und Tipps für die asynchrone Programmierung in Hilo (Windows Store-Apps mit C++ und XAML)

[ Dieser Artikel richtet sich an Windows 8.x- und Windows Phone 8.x-Entwickler, die Windows-Runtime-Apps schreiben. Wenn Sie für Windows 10 entwickeln, finden Sie weitere Informationen unter neueste Dokumentation]

Aus: Umfassende Entwicklung einer Windows Store-App mit C++ und XAML: Hilo

Leitfaden-Logo

Vorherige Seite | Nächste Seite

Hilo enthält zahlreiche Beispiele für Fortsetzungsketten in C++. Diese stellen ein gängiges Muster für die asynchrone Programmierung in Windows Store-Apps dar. In diesem Dokument finden Sie eine Reihe von Tipps und Richtlinien zur Verwendung von Fortsetzungsketten. Außerdem enthält das Dokument Beispiele zur Verwendung dieser Ketten in Ihrer App.

Download

Herunterladen des Hilo-Beispiels
Buch herunterladen (PDF)

Anweisungen zu dem heruntergeladenen Code finden Sie unter Erste Schritte mit Hilo.

Im Einzelnen werden Sie Folgendes lernen:

  • Bewährte Methoden für die asynchrone Programmierung in Windows Store-Apps mit C++.
  • Vorgehensweise zum Abbrechen ausstehender asynchroner Vorgänge.
  • Vorgehensweise zum Behandeln von Ausnahmen in asynchronen Vorgängen.
  • Verwenden von Parallelaufgaben mit Windows Store-Apps in Visual C++.

Betrifft

  • Windows-Runtime für Windows 8
  • Visual C++-Komponentenerweiterungen (C++/CX)
  • Parallel Patterns Library (PPL)

Beispiele zur Verwendung des Fortsetzungskettenmusters

Die in Hilo verwendeten Methoden der asynchronen Programmierung lassen sich unter dem allgemeinen Muster der Fortsetzungskette oder ".then ladder" (Dot-Then-Leiter) subsummieren. Eine Fortsetzungskette ist eine Sequenz von PPL-Aufgaben, die durch Datenflussbeziehungen verbunden sind. Die Ausgabe einer Aufgabe wird als Eingabe für die nächste Fortsetzung in der Kette verwendet. Am Beginn der Kette steht i. d. R. eine Aufgabe, die einen asynchronen Windows-Runtime- Vorgang einschließt (eine von IAsyncInfo abgeleitete Schnittstelle). Informationen über Fortsetzungsketten finden Sie in diesem Handbuch unter Schreiben von modernem C++-Code für Windows Store-Apps.

Hier sehen Sie ein Beispiel für eine Fortsetzungskette. Am Beginn steht eine Aufgabe, die eine von IAsyncInfo abgeleitete Schnittstelle einschließt.

FileSystemRepository.cpp


task<IPhotoImage^> FileSystemRepository::GetSinglePhotoAsync(String^ photoPath)
{
    assert(IsMainThread());
    String^ query = "System.ParsingPath:=\"" + photoPath + "\"";    
    auto fileQuery = CreateFileQuery(KnownFolders::PicturesLibrary, query, IndexerOption::DoNotUseIndexer);
    shared_ptr<ExceptionPolicy> policy = m_exceptionPolicy;
    return create_task(fileQuery->GetFilesAsync(0, 1)).then([policy](IVectorView<StorageFile^>^ files) -> IPhotoImage^
    {
        if (files->Size > 0)
        {
            IPhotoImage^ photo = (ref new Photo(files->GetAt(0), ref new NullPhotoGroup(), policy))->GetPhotoImage();
            create_task(photo->InitializeAsync());
            return photo;
        }
        else
        {
            return nullptr;
        }
    }, task_continuation_context::use_current());
}


Der erwartete Aufrufkontext für die FileSystemRepository::GetSinglePhotoAsync-Methode in diesem Beispiel ist der Hauptthread. Diese Methode erstellt eine PPL-Aufgabe, mit der das Ergebnis der GetFilesAsync-Methode eingeschlossen wird. Die GetFilesAsync-Methode ist eine Hilfsfunktion, die ein IAsyncOperation< IVectorView<StorageFile^>^>^-Handle zurückgibt.

Die task::then-Methode erstellt Fortsetzungen, die, abhängig von der jeweiligen Konfiguration, im gleichen Thread oder in einem anderen Thread als die vorhergehende Aufgabe ausgeführt werden. Dieses Beispiel enthält eine Fortsetzung.

Hinweis   Da die von der concurrency::create_task-Funktion erstellte Aufgabe einen von IAsyncInfo abgeleiteten Typ einschließt, werden ihre Fortsetzungen standardmäßig in dem Kontext ausgeführt, der die Fortsetzungskette erstellt. Im Beispiel handelt es sich dabei um den Hauptthread.
 

Einige Vorgänge müssen im Hauptthread ausgeführt werden. Dazu gehören beispielsweise Vorgänge, die mit XAML-Steuerelementen interagieren. Das PhotoImage-Objekt kann beispielsweise an ein XAML-Steuerelement gebunden werden. Es muss daher im Hauptthread instanziiert werden. Andererseits dürfen Sperrvorgänge nur von einem Hintergrundthread und nicht vom Hauptthread der App aufgerufen werden. Um Fehler bei der Programmierung zu vermeiden, müssen Sie daher wissen, ob die jeweilige Fortsetzung den Haupt- oder einen Hintergrundthread aus dem Threadpool verwendet.

Das Fortsetzungskettenmuster kann, abhängig von Ihrer Situation, auf unterschiedliche Weise verwendet werden. Nachstehend finden Sie einige Varianten des einfachen Fortsetzungskettenmusters:

Wert- und aufgabenbasierte Fortsetzungen.

Die Aufgabe, die vor einer Fortsetzung steht, wird als vorhergehende Aufgabe oder Vorgänger bezeichnet. Wenn die vorhergehende Aufgabe den Typ task<T> aufweist, kann eine Fortsetzung der Aufgabe den Typ T oder den Typ task<T> als Argumenttyp akzeptieren. Fortsetzungen, die den Typ T akzeptieren, sind wertbasierte Fortsetzungen. Fortsetzungen, die den Typ task<T> akzeptieren, sind aufgabenbasierte Fortsetzungen.

Das Argument einer wertbasierten Fortsetzung ist der Rückgabewert der Arbeitsfunktion für die vorhergehende Aufgabe der Fortsetzung. Das Argument einer aufgabenbasierten Fortsetzung ist die vorhergehende Aufgabe selbst. Mit der task::get-Methode können Sie die Ausgabe der vorhergehenden Aufgabe abfragen.

Wert- und aufgabenbasierte Fortsetzungen weisen hinsichtlich ihres Verhaltens mehrere Unterschiede auf. Wertbasierte Fortsetzungen werden nicht ausgeführt, wenn die vorhergehende Aufgabe aufgrund einer Ausnahme beendet wurde. Aufgabenbasierte Fortsetzungen werden hingegen unabhängig vom Ausnahmestatus der vorhergehenden Aufgabe ausgeführt. Darüber hinaus erben wertbasierte Fortsetzungen das Abbruchtoken standardmäßig von ihrem Vorgänger. Bei aufgabenbasierten Fortsetzungen ist dies nicht der Fall.

Die meisten Fortsetzungen sind wertbasierte Fortsetzungen. Aufgabenbasierte Fortsetzungen werden in Szenarien verwendet, in denen Ausnahmen und Abbrüche behandelt werden. Beispiele hierzu finden Sie im weiteren Verlauf dieses Themas unter Externes Abbrechen von Fortsetzungsketten ermöglichen und Verwenden von aufgabenbasierten Fortsetzungen zur Behandlung von Ausnahmen.

Entpackte Aufgaben

Die Arbeitsfunktion einer PPL-Aufgabe gibt i. a. R. den Typ T zurück, wenn die Aufgabe den Typ task<T> aufweist. Neben dem Typ T sind jedoch auch weitere Rückgaben möglich. Sie können auch einen task<T>-Wert von der Arbeitsfunktion zurückgeben und an die create_task- oder an die task::then-Funktion übergeben. Dabei erwarten Sie u. U., dass von PPL eine Aufgabe mit dem Typ task<task<T>> erstellt wird. Dies ist jedoch nicht der Fall. Stattdessen weist die resultierende Aufgabe den Typ task<T> auf. Die automatische Transformation von task<task<T>> in task<T> wird als Entpacken einer Aufgabe bezeichnet.

Das Entpacken erfolgt auf der Typebene. PPL plant die innere Aufgabe und verwendet sie als Ergebnis für die äußere Aufgabe. Die äußere Aufgabe wird nach Abschluss der inneren Aufgabe abgeschlossen.

Entpackte Aufgaben

Die Aufgabe 1 im Diagramm weist eine Arbeitsfunktion auf, die einen ganzzahligen Wert zurückgibt. Der Typ von Aufgabe 1 ist "task<int>". Die Aufgabe 2 weist eine Arbeitsfunktion auf, die "task<int>" zurückgibt; dies ist die Aufgabe 2a im Diagramm. Aufgabe 2 wartet, bis Aufgabe 2a abgeschlossen ist. Anschließend wird das Ergebnis der Arbeitsfunktion von Aufgabe 2a als Ergebnis von Aufgabe 2 zurückgegeben.

Das Entpacken von Aufgaben hilft Ihnen, die Bedingungslogik von Datenflüssen in Netzwerke mit verwandten Aufgaben einzubinden. Szenarien und Beispiele, in denen PPL zum Entpacken von Aufgaben erforderlich ist, finden Sie unter Verwenden geschachtelter Fortsetzungen für Bedingungslogik im weiteren Verlauf dieser Seite. Eine Tabelle mit Rückgabetypen finden Sie unter "Rückgabetypen für Lambda-Funktionen und Aufgaben" in Asynchrone Programmierung in C++.

Externes Abbrechen von Fortsetzungsketten ermöglichen

Sie können Fortsetzungsketten erstellen, die auf externe Anforderungen zum Abbrechen reagieren. Wenn Sie beispielsweise die Bildbrowserseite in Hilo anzeigen, startet die Seite einen asynchronen Vorgang, mit dem Gruppen von Fotos nach Monaten erstellt und angezeigt werden. Wenn Sie die Seite vor dem Abschluss des Anzeigevorgangs verlassen, wird der ausstehende Vorgang zum Erstellen von Steuerelementen für die Gruppierung nach Monaten abgebrochen. Erforderliche Schritte:

  1. Erstellen Sie ein concurrency::cancellation_token_source-Objekt.
  2. Fragen Sie die Quelle des Abbruchtokens für ein concurrency::cancellation_token mit der cancellation_token_source::get_token-Methode ab.
  3. Übergeben Sie das Abbruchtoken als Argument an die concurrency::create_task -Funktion der einleitenden Aufgabe in der Fortsetzungskette oder an die task::then-Methode einer beliebigen Fortsetzung. Diese Schritte müssen nur ein Mal für jede Fortsetzungskette ausgeführt werden, da von nachfolgenden Fortsetzungsaufgaben standardmäßig der Abbruchkontext der vorhergehenden Aufgabe verwendet wird.
  4. Um die Ausführung der Fortsetzungskette abzubrechen, rufen Sie die cancel-Methode der Quelle des Abbruchtokens von einem beliebigen Threadkontext auf.

Hier ist ein Beispiel.

ImageBrowserViewModel.cpp


void ImageBrowserViewModel::StartMonthAndYearQueries()
{
    assert(IsMainThread());
    assert(!m_runningMonthQuery);
    assert(!m_runningYearQuery);
    assert(m_currentMode == Mode::Active || m_currentMode == Mode::Running || m_currentMode == Mode::Pending);

    m_cancellationTokenSource = cancellation_token_source();
    auto token = m_cancellationTokenSource.get_token();
    StartMonthQuery(m_currentQueryId, token);
    StartYearQuery(m_currentQueryId, token);
}

Die StartMonthAndYearQueries-Methode erstellt die Objekte concurrency::cancellation_token_source und concurrency::cancellation_token und übergibt das Abbruchtoken an die Methoden StartMonthQuery und StartYearQuery. Dies ist die Implementierung der StartMonthQuery-Methode.

ImageBrowserViewModel.cpp


void ImageBrowserViewModel::StartMonthQuery(int queryId, cancellation_token token)
{
    m_runningMonthQuery = true;
    OnPropertyChanged("InProgress");
    m_photoCache->Clear();
    run_async_non_interactive([this, queryId, token]()
    {
        // if query is obsolete, don't run it.
        if (queryId != m_currentQueryId) return;

        m_repository->GetMonthGroupedPhotosWithCacheAsync(m_photoCache, token).then([this, queryId](task<IVectorView<IPhotoGroup^>^> priorTask)
        {
            assert(IsMainThread());
            if (queryId != m_currentQueryId)  
            {
                // Query is obsolete. Propagate exception and quit.
                priorTask.get();
                return;
            }

            m_runningMonthQuery = false;
            OnPropertyChanged("InProgress");
            if (!m_runningYearQuery)
            {
                FinishMonthAndYearQueries();
            }
            try
            {     
                // Update display with results.
                m_monthGroups->Clear();                   
                for (auto group : priorTask.get())
                {  
                    m_monthGroups->Append(group);
                }
                OnPropertyChanged("MonthGroups");
            }
            // On exception (including cancellation), remove any partially computed results and rethrow.
            catch (...)
            {
                m_monthGroups = ref new Vector<IPhotoGroup^>();
                throw;
            }
        }, task_continuation_context::use_current()).then(ObserveException<void>(m_exceptionPolicy));
    });
}


Die StartMonthQuery-Methode erstellt eine Fortsetzungskette. Der Beginn der Fortsetzungskette und die erste Fortsetzungsaufgabe der Kette werden von der GetMonthGroupedPhotosWithCacheAsync-Methode der FileSystemRepository-Klasse erstellt.

FileSystemRepository.cpp


task<IVectorView<IPhotoGroup^>^> FileSystemRepository::GetMonthGroupedPhotosWithCacheAsync(shared_ptr<PhotoCache> photoCache, concurrency::cancellation_token token)
{
    auto queryOptions = ref new QueryOptions(CommonFolderQuery::GroupByMonth);
    queryOptions->FolderDepth = FolderDepth::Deep;
    queryOptions->IndexerOption = IndexerOption::UseIndexerWhenAvailable;
    queryOptions->Language = CalendarExtensions::ResolvedLanguage();
    auto fileQuery = KnownFolders::PicturesLibrary->CreateFolderQueryWithOptions(queryOptions);
    m_monthQueryChange = (m_imageBrowserViewModelCallback != nullptr) ? ref new QueryChange(fileQuery, m_imageBrowserViewModelCallback) : nullptr;

    shared_ptr<ExceptionPolicy> policy = m_exceptionPolicy;
    auto sharedThis = shared_from_this();
    return create_task(fileQuery->GetFoldersAsync()).then([this, photoCache, sharedThis, policy](IVectorView<StorageFolder^>^ folders) 
    {
        auto temp = ref new Vector<IPhotoGroup^>();
        for (auto folder : folders)
        {
            auto photoGroup = ref new MonthGroup(photoCache, folder, sharedThis, policy);
            temp->Append(photoGroup);
        }
        return temp->GetView();
    }, token);
}

Der Code übergibt in der ersten Fortsetzungsaufgabe ein Abbruchtoken an die task::then-Methode.

Die Ausführung der Abfragen zu Jahr und Monat kann einige Sekunden dauern, wenn viele Bilder verarbeitet werden müssen. Während ihrer Ausführung verlässt der Benutzer möglicherweise die Seite. In diesem Fall werden die Abfragen abgebrochen. Die OnNavigatedFrom-Methode in der Hilo-App ruft die CancelMonthAndYearQueries-Methode auf. Das CancelMonthAndYearQueries-Element der ImageBrowserViewModel-Klasse ruft die cancel-Methode der Quelle des Abbruchtokens auf.

ImageBrowserViewModel.cpp


void ImageBrowserViewModel::CancelMonthAndYearQueries()
{
    assert(m_currentMode == Mode::Running);

    if (m_runningMonthQuery)
    {
        m_runningMonthQuery = false;
        OnPropertyChanged("InProgress");
    }
    m_runningYearQuery = false;
    m_currentQueryId++;
    m_cancellationTokenSource.cancel();
}

Abbruchvorgänge in PPL sind kooperativ. Sie können das Abbruchverhalten der von Ihnen implementierten Aufgaben kontrollieren. Nähere Informationen zu den Folgen eine Aufrufs der Abbruchmethode für die Quelle des Abbruchtokens finden Sie unter "Abbrechen von Aufgaben" in Asynchrone Programmierung in C++.

Weitere Möglichkeiten zum Signalisieren von Abbrüchen

Wertbasierte Fortsetzungen werden nicht gestartet, wenn die cancel-Methode des zugeordneten Abbruchtokens abgebrochen wurde. Sie können auch die cancel-Methode aufrufen, während eine Aufgabe in der Fortsetzungskette ausgeführt wird. Wenn Sie wissen möchten, ob eine externe Anforderung zum Abbruch während der Ausführung einer Aufgabe übermittelt wurde, können Sie zu diesem Zweck die concurrency::is_task_cancellation_requested- Funktion aufrufen. Die is_task_cancellation_requested- Funktion überprüft den Status des Abbruchtokens für die aktuelle Aufgabe. Diese Funktion gibt true zurück, wenn die cancel-Methode für das Quellobjekt des Abbruchtokens aufgerufen wurde, mit dem das Abbruchtoken erstellt wurde. (Ein Beispiel dazu finden Sie unter Verwenden von C++ am Beispiel des Reise-Optimierers von Bing Maps.)

Hinweis  Sie können is_task_cancellation_requested aus dem Hauptteil der Arbeitsfunktion für eine beliebige Aufgabe aufrufen. Dazu gehören auch Aufgaben, die von der create_task-Funktion, der create_async-Funktion und der task::then-Methode erstellt wurden.
 
Tipp  Rufen Sie is_task_cancellation_requested nicht in jedem Durchlauf einer engen Schleife auf Allgemein gilt, dass wenn Sie eine Abbruchanforderung in einer laufende Aufgabe erkennen möchten, ein häufiges Abrufen von Abbrüchen empfehlenswert ist. Auf diese Weise steht ausreichend Zeit zur Reaktion zur Verfügung, ohne dass Auswirkungen auf die Leistung Ihrer App zu befürchten wären, weil keine Abbrüche abgerufen wurden.
 

Nachdem Sie erkannt haben, dass eine Abbruchanforderung von einer laufenden Aufgabe verarbeitet werden muss, führen Sie die für die App erforderlichen Bereinigungsaufgaben aus. Anschließend rufen Sie die concurrency::cancel_current_task-Funktion auf, um die Aufgabe zu beenden und die Laufzeit über den Abbruch zu benachrichtigen.

Abbruchtoken stellen nicht die einzige Möglichkeit dar, Anforderungen zum Abbruch zu übermitteln. Beim Aufrufen asynchroner Bibliotheksfunktionen erhalten Sie manchmal einen speziellen Signalwert wie nullptr von der vorhergehenden Aufgabe. Damit soll angegeben werden, dass ein Abbruch angefordert wurde.

Abbrechen von asynchronen Vorgängen, die in Aufgaben eingeschlossen sind

PPL-Aufgaben unterstützen Abbruchvorgänge in Apps mit asynchronen Vorgängen der Windows Runtime. Wenn Sie eine von IAsyncInfo abgeleitete Schnittstelle in eine Aufgabe einschließen, wird der asynchrone Vorgang von PPL (durch Aufrufen der IAsyncInfo::Cancel-Methode) benachrichtigt, wenn eine Anforderung zum Abbruch eintrifft.

Hinweis  Das concurrency::cancellation_token von PPL ist ein C++-Typ, der die ABI nicht verlassen kann. Das Übergeben als Argument an die öffentliche Methode einer öffentlichen Referenzklasse ist somit nicht möglich. Wenn Sie ein Abbruchtoken für diesen Fall benötigen, können Sie den Aufruf der öffentlichen asynchronen Methode für Ihre öffentliche Referenzklasse in eine PPL-Aufgabe mit einem Abbruchtoken einschließen. Anschließend rufen Sie die concurrency::is_task_cancellation_requested- Funktion in der Arbeitsfunktion der create_async- Funktion in der asynchronen Methode auf. Die is_task_cancellation_requested- Funktion kann auf das Token der PPL-Aufgabe zugreifen und gibt true zurück, wenn das Token übermittelt wurde.
 

Verwenden von aufgabenbasierten Fortsetzungen zur Behandlung von Ausnahmen

PPL-Aufgaben unterstützen die verzögerte Ausnahmebehandlung. Sie können eine aufgabenbasierte Fortsetzung verwenden, um Ausnahmen abzufangen, die in einem beliebigen Schritt einer Fortsetzungskette auftreten.

In Hilo wird am Ende jeder Fortsetzungskette eine systematische Prüfung auf Ausnahmen durchgeführt. Zur Erleichterung dieses Vorgangs wurde eine Hilfsklasse erstellt, die Ausnahmen sucht und unter Berücksichtigung einer konfigurierbaren Richtlinie behandelt. Hier ist der Code.

TaskExceptionsExtensions.h


    template<typename T>
    struct ObserveException
    {
        ObserveException(std::shared_ptr<ExceptionPolicy> handler) : m_handler(handler)
        {
        }

        concurrency::task<T> operator()(concurrency::task<T> antecedent) const
        {
            T result;
            try 
            {
                result = antecedent.get();
            }
            catch(const concurrency::task_canceled&)
            {
                // don't need to run canceled tasks through the policy
            }
            catch(const std::exception&)
            {
                auto translatedException = ref new Platform::FailureException();
                m_handler->HandleException(translatedException);
                throw;
            }
            catch(Platform::Exception^ ex)
            {
                m_handler->HandleException(ex);
                throw;
            }
            return antecedent;
        }

    private:
        std::shared_ptr<ExceptionPolicy> m_handler;
    };


Zusammensetzen der Ausgabe mehrerer Fortsetzungen

Fortsetzungsketten verwenden eine Programmierung im Datenflussstil. Dabei wird der Rückgabewert einer vorherigen Aufgabe als Eingabe für eine Fortsetzungsaufgabe verwendet. Es kann jedoch sein, das der Wert der vorhergehenden Aufgabe nicht alle Elemente enthält, die für den nächsten Schritt erforderlich sind. Beispielweise kann es erforderlich sein, das Ergebnis zweier Fortsetzungsaufgaben zusammenzuführen, bevor eine dritte Fortsetzung ausgeführt werden kann. Dieser Fall kann auf mehrere Arten gelöst werden. In Hilo werden Zwischenwerte mit einem freigegebenen Zeiger (std::shared_ptr) vorübergehend auf dem Heap abgelegt.

Im nächsten Diagramm sehen Sie beispielsweise die Beziehungen für den Datenfluss und die Ablaufsteuerung von Hilos ThumbnailGenerator::CreateThumbnailFromPictureFileAsync-Methode. Diese Methode verwendet eine Fortsetzungskette zum Erstellen von Miniaturbildern. Das Erstellen eines Miniaturbilds aus einer Bilddatei involviert asynchrone Schritte, die nicht in ein geradliniges Datenflussmuster passen.

Die durchgehenden Ovale im Diagramm stellen asynchrone Windows-Runtime-Vorgänge dar. Bei den gestrichelten Ovalen handelt es sich um Aufgaben zum Aufrufen asynchroner Funktionen. Die Bögen stehen für Ein- und Ausgaben. Die gestrichelten Pfeile sind Abhängigkeiten von Ablaufsteuerungen für Vorgänge mit Nebeneffekten. Die Ziffern geben die Reihenfolge an, in der die Vorgänge in der Fortsetzungskette der CreateThumbnailFromPictureFileAsync-Methode ausgeführt werden.

CreateThumbnailFromPictureFileAsync-Methode

Aus dem Diagramm wird ersichtlich, dass die Interaktionen zu komplex für einen linearen Datenfluss sind. So erfordert der synchrone Vorgang "Set pixel data" Eingaben aus drei verschiedenen asynchronen Vorgängen, und eine Eingabe wird geändert. Dies hat zur Folge, dass die Sequenzierung eines nachfolgenden asynchronen Löschvorgangs eingeschränkt wird. Hier sehen Sie den Code für dieses Beispiel.

ThumbnailGenerator.cpp


task<InMemoryRandomAccessStream^> ThumbnailGenerator::CreateThumbnailFromPictureFileAsync(
    StorageFile^ sourceFile, 
    unsigned int thumbSize)
{
    (void)thumbSize; // Unused parameter
    auto decoder = make_shared<BitmapDecoder^>(nullptr);
    auto pixelProvider = make_shared<PixelDataProvider^>(nullptr);
    auto resizedImageStream = ref new InMemoryRandomAccessStream();
    auto createThumbnail = create_task(
        sourceFile->GetThumbnailAsync(
        ThumbnailMode::PicturesView, 
        ThumbnailSize));

    return createThumbnail.then([](StorageItemThumbnail^ thumbnail)
    {
        IRandomAccessStream^ imageFileStream = 
            static_cast<IRandomAccessStream^>(thumbnail);

        return BitmapDecoder::CreateAsync(imageFileStream);

    }).then([decoder](BitmapDecoder^ createdDecoder)
    {
        (*decoder) = createdDecoder;
        return createdDecoder->GetPixelDataAsync( 
            BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            ref new BitmapTransform(),
            ExifOrientationMode::IgnoreExifOrientation,
            ColorManagementMode::ColorManageToSRgb);

    }).then([pixelProvider, resizedImageStream](PixelDataProvider^ provider)
    {
        (*pixelProvider) = provider;
        return BitmapEncoder::CreateAsync(
            BitmapEncoder::JpegEncoderId, 
            resizedImageStream);

    }).then([pixelProvider, decoder](BitmapEncoder^ createdEncoder)
    {
        createdEncoder->SetPixelData(BitmapPixelFormat::Rgba8,
            BitmapAlphaMode::Straight,
            (*decoder)->PixelWidth,
            (*decoder)->PixelHeight,
            (*decoder)->DpiX,
            (*decoder)->DpiY,
            (*pixelProvider)->DetachPixelData());
        return createdEncoder->FlushAsync();

    }).then([resizedImageStream]
    {
        resizedImageStream->Seek(0);
        return resizedImageStream;
    });
}


In diesem Beispiel wird die std::make_shared-Funktion aufgerufen, um freigegebene Container für Handles für den Decoder sowie Pixelanbieterobjekte zu erstellen, die von den Fortsetzungen erstellt werden. Der *-Operator (für die Dereferenzierung) ermöglicht den Fortsetzungen das Festlegen der freigegebenen Container sowie das Lesen der zugehörigen Werte.

Verwenden geschachtelter Fortsetzungen für Bedingungslogik

Geschachtelte Konfigurationen ermöglichen Ihnen, das Erstellen von Teilen einer Fortsetzungskette bis zur Laufzeit zu verschieben. Geschachtelte Konfigurationen sind hilfreich, wenn die Kette Bedingungsaufgaben enthält und lokale Variablen von Aufgaben in einer Fortsetzungskette zur Verwendung in nachfolgenden Fortsetzungsaufgaben erfasst werden müssen. Geschachtelte Fortsetzungen erfordern ein Entpacken von Aufgaben.

In Hilo werden geschachtelte Konfigurationen bei Vorgängen zum Drehen von Bildern zur Behandlung der Bedingungslogik verwendet. Das Verhalten des Drehvorgangs ist abhängig davon, ob das Dateiformat des gedrehten Bilds die Ausrichtungseigenschaft Exchangeable Image File Format (EXIF) unterstützt. Hier ist der Code.

RotateImageViewModel.cpp


concurrency::task<BitmapEncoder^> RotateImageViewModel::SetEncodingRotation(BitmapEncoder^ encoder, shared_ptr<ImageEncodingInformation> encodingInfo, float64 rotationAngle, concurrency::task_continuation_context backgroundContext)
{
    // If the file format supports Exif orientation then update the orientation flag
    // to reflect any user-specified rotation. Otherwise, perform a hard rotate 
    // using the BitmapTransform class.
    auto encodingTask = create_empty_task();
    if (encodingInfo->usesExifOrientation)
    {
        // Try encoding with Exif with updated values.
        auto currentExifOrientationDegrees = ExifExtensions::ConvertExifOrientationToDegreesRotation(ExifRotations(encodingInfo->exifOrientation));
        auto newRotationAngleToApply = CheckRotationAngle(safe_cast<unsigned int>(rotationAngle + currentExifOrientationDegrees));
        auto exifOrientationToApply = ExifExtensions::ConvertDegreesRotationToExifOrientation(newRotationAngleToApply);
        auto orientedTypedValue = ref new BitmapTypedValue(static_cast<unsigned short>(exifOrientationToApply), PropertyType::UInt16);

        auto properties = ref new Map<String^, BitmapTypedValue^>();
        properties->Insert(EXIFOrientationPropertyName, orientedTypedValue);

        encodingTask = encodingTask.then([encoder, properties]
        { 
            assert(IsBackgroundThread());
            return encoder->BitmapProperties->SetPropertiesAsync(properties);
        }, backgroundContext).then([encoder, encodingInfo] (task<void> setPropertiesTask)
        {
            assert(IsBackgroundThread());

            try 
            {
                setPropertiesTask.get();
            }
            catch(Exception^ ex)
            {
                switch(ex->HResult)
                {
                case WINCODEC_ERR_UNSUPPORTEDOPERATION:
                case WINCODEC_ERR_PROPERTYNOTSUPPORTED:
                case E_INVALIDARG:
                    encodingInfo->usesExifOrientation = false;
                    break;
                default:
                    throw;
                }
            }

        }, backgroundContext);
    }

    return encodingTask.then([encoder, encodingInfo, rotationAngle]
    {
        assert(IsBackgroundThread());
        if (!encodingInfo->usesExifOrientation)
        {
            BitmapRotation rotation = static_cast<BitmapRotation>((int)floor(rotationAngle / 90));
            encoder->BitmapTransform->Rotation = rotation;
        }
        return encoder;
    });
}


Anzeigen des Status asynchroner Vorgänge

Der Status laufender Vorgänge wird von der Hilo.App nicht angezeigt. Sie können jedoch eine entsprechende Funktion implementieren. Weitere Informationen finden Sie im Blogbeitrag Keeping apps fast and fluid with asynchrony in the Windows Runtime. Dort finden Sie auch einige Beispiele zur Verwendung der IAsyncActionWithProgress<TProgress>- und der IAsyncOperationWithProgress<TProgress, TResult>-Schnittstelle.

Hinweis  Hilo zeigt den animierten Statusring bei langfristigen asynchronen Vorgängen an. Weitere Informationen über das Anzeigen des Status in Hilo finden Sie in diesem Handbuch unter ProgressRing.
 

Erstellen von Hintergrundaufgaben mit create_async für Interoperabilitätsszenarien

In einigen Fällen müssen die von IAsyncInfo abgeleiteten Schnittstellen unmittelbar in den von Ihnen definierten Klassen verwendet werden. So dürfen die Signaturen öffentlicher Methoden von öffentlichen Referenzklassen in Ihrer App keine Typen wie task<T> enthalten, bei denen es sich nicht um einen Referenztyp handelt . Stattdessen machen Sie asynchrone Vorgänge mit einer der Schnittstellen verfügbar, die von der IAsyncInfo-Schnittstelle abgeleitet werden. (Eine öffentliche Referenzklasse ist eine C++-Klasse, die mit öffentlicher Sichtbarkeit und dem C++/CX-Referenzschlüsselwort deklariert wurde.)

Mit der concurrency::create_async-Funktion können Sie eine PPL-Aufgabe als IAsyncInfo-Objekt verfügbar machen.

Weitere Informationen über die create_async-Funktion finden Sie unter Erstellen asynchroner Vorgänge in C++ für Windows Store-Apps.

Senden von Funktionen an den Hauptthread

Das Fortsetzungskettenmuster eignet sich gut für Vorgänge, die vom Benutzer mit XAML-Steuerelementen initiiert werden. In aller Regel wird ein Vorgang durch Aufrufen einer Ansichtsmodelleigenschaft im Hauptthread gestartet, die an die Eigenschaft eines XAML-Steuerelements gebunden ist. Das Ansichtsmodell erstellt eine Fortsetzungskette zur Ausführung einiger Aufgaben im Hauptthread und einiger Aufgaben im Hintergrund. Die Aktualisierung der Benutzeroberfläche mit den Ergebnissen des Vorgangs wird im Hauptthread durchgeführt.

Nicht alle Aktualisierungen der Benutzeroberfläche erfolgen jedoch als Reaktion auf Vorgänge im Hauptthread. Einige Aktualisierungen können auch auf externe Quellen zurückzuführen sein, beispielsweise Netzwerkpakete und -Geräte. Wenn dies der Fall ist, muss Ihre App möglicherweise XAML-Steuerelemente im Hauptthread außerhalb eines Fortsetzungskontexts aktualisieren. Möglicherweise eignet sich eine Fortsetzungskette auch nicht für Ihre Zwecke.

Sie haben zwei Möglichkeiten, mit dieser Situation umzugehen. Eine Möglichkeit besteht darin, eine Aufgabe zu erstellen, die keine Aktion ausführt. Anschließend planen Sie eine Fortsetzung dieser Aufgabe, die mit dem Ergebnis der concurrency::task_continuation_context::use_current-Funktion als Argument für die task::then-Methode im Hauptthread ausgeführt wird. Auf diese Weise können Ausnahmen im Rahmen des Vorgangs ganz einfach behandelt werden. Um die Ausnahmen zu behandeln, können Sie eine aufgabenbasierte Fortsetzung verwenden. Die zweite Option ist Win32-Programmierern bereits bekannt. Mit der RunAsync-Methode der CoreDispatcher-Klasse können Sie eine Funktion im Hauptthread ausführen. Über die Dispatcher-Eigenschaft des CoreWindow-Objekts können Sie auf ein CoreDispatcher-Objekt zugreifen. Wenn Sie die RunAsync-Methode verwenden, müssen Sie festlegen, wie Ausnahmen behandelt werden sollen. Standardmäßig werden alle Ausnahmen, die beim Aufrufen von RunAsync auftreten, verworfen, wenn der Rückgabewert ignoriert wird. Um dies zu verhindern, können Sie den IAsyncAction^-Handle, der von der RunAsync-Methode zurückgegeben wird, in eine PPL-Aufgabe einschließen und eine aufgabenbasierte Fortsetzungen für alle Ausnahmen der Aufgabe hinzufügen.

Hier ist ein Beispiel aus Hilo.

TaskExtensions.cpp


void run_async_non_interactive(std::function<void ()>&& action)
{
    Windows::UI::Core::CoreWindow^ wnd = Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow;
    assert(wnd != nullptr);

    wnd->Dispatcher->RunAsync(
        Windows::UI::Core::CoreDispatcherPriority::Low, 
        ref new Windows::UI::Core::DispatchedHandler([action]()
    {
        action();
    })); 
} 


Mit dieser Hilfsfunktion werden Funktionen ausgeführt, die mit niedriger Priorität an den Hauptthread gesendet werden, um keine Konflikte mit Aktionen auf der Benutzeroberfläche auszulösen.

Verwenden der asynchronen Agents-Bibliothek

Agents sind ein hilfreiches Modell für die parallele Programmierung. Dies gilt insbesondere in streambasierten Anwendungen wie Benutzeroberflächen. Verwenden Sie Meldungspuffer zur Interaktion mit Agents. Weitere Informationen finden Sie unter Asynchrone Agents-Bibliothek.

[Nach oben]

Tipps für die asynchrone Programmierung in Windows Store-Apps mit C++.

Nachfolgend finden Sie einige Tipps und Richtlinien zum Schreiben von effizientem asynchronen Code in Windows Store-Apps mit C++.

Programmieren Sie nicht direkt mit Threads.

Windows Store-Apps können neue Threads nicht direkt erstellen. Unabhängig davon können Sie jedoch den vollen Funktionsumfang von C++-Bibliotheken wie der PPL und der asynchronen Agents-Bibliothek nutzen. Verwenden Sie die Funktionen dieser Bibliotheken generell für neuen Code. Programmieren Sie nicht direkt mit Threads.

Hinweis  Beim Portieren von vorhandenem Code können Sie auch weiterhin Threadpoolthreads verwenden. Ein Codebeispiel finden Sie unter Beispiel für Threadpool .
 

Verwenden von "Async" im Namen von asynchronen Funktionen

Wenn Sie eine asynchrone Funktion oder Methode erstellen, wird empfohlen, "Async" im Namen der Funktion oder Methode zu verwenden, um dies zu verdeutlichen. Diese Konvention gilt für alle asynchronen Funktionen und Methoden in Hilo. Die Windows Runtime verwendet die Konvention darüber hinaus für alle asynchronen Funktionen und Methoden.

Schließen Sie alle asynchronen Vorgänge der Windows Runtime in PPL-Aufgaben ein

In Hilo werden alle asynchronen Vorgänge von der Windows Runtime mit der concurrency::create_task-Funktion in einer PPL-Aufgabe eingeschlossen. PPL-Aufgaben lassen sich leichter bearbeiten als die untergeordneten asynchronen Schnittstellen der Windows Runtime. Fehler, die im Rahmen von asynchronen Windows-Runtime-Vorgängen auftreten, werden von Ihrer App ignoriert, es sei denn, Sie verwenden PPL (oder schreiben benutzerdefinierten Code zum Einschließen, der HRESULT-Werte überprüft). Bei einem asynchronen Vorgang, der in eine Aufgabe eingeschlossen ist, werden Laufzeitfehler dagegen automatisch unter Verwendung verzögerter PPL-Ausnahmen weitergegeben.

Darüber hinaus enthalten PPL-Aufgaben eine einfache zu verwendende Syntax für das Fortsetzungskettenmuster.

Einzige Ausnahme bilden hier die Fire-and-Forget-Funktionen. Bei diesen Funktionen ist eine Benachrichtigung über Ausnahmen definitionsgemäß nicht erwünscht. Dies ist jedoch eine Ausnahme.

Zurückgeben von PPL-Aufgaben von internen asynchronen Funktionen in der App

Es wird empfohlen, PPL-Aufgaben von allen asynchronen Funktionen und Methoden in Ihrer App zurückzugeben. Ausnahmen bilden öffentliche, geschützte oder öffentliche geschützte Methoden öffentlicher Referenzklassen. Verwenden Sie die von IAsyncInfo abgeleiteten Schnittstellen nur für öffentliche asynchrone Methoden öffentlicher Referenzklassen.

Zurückgeben von von IAsyncInfo abgeleiteten Schnittstellen von öffentlichen asynchronen Methoden öffentlicher Referenzklassen

Alle öffentlichen, geschützten oder öffentlichen geschützten Methoden der öffentlichen Referenzklassen Ihrer App müssen für den Compiler einen IAsyncInfo-Typ zurückgeben. Die tatsächliche Schnittstelle muss ein Handle für eine der 4 Schnittstellen sein, die von der IAsyncInfo-Schnittstelle abgeleitet werden:

Im Allgemeinen machen Apps öffentliche asynchrone Methoden in öffentlichen Referenzklassen nur im Zusammenhang mit der Interoperabilität der binären Anwendungsschnittstelle (ABI) verfügbar.

Verwenden von öffentlichen Referenzklassen nur für die Interoperabilität

Verwenden Sie öffentliche Referenzklassen für die Interoperabilität in der ABI. Beispielsweise müssen Sie öffentliche Referenzklassen für Ansichtsmodellklassen verwenden, die Eigenschaften für XAML-Datenbindungen verfügbar machen.

Deklarieren Sie interne App-Klassen nicht mit dem ref-Schlüsselwort. In einigen Fällen muss jedoch eine Ausnahme gemacht werden. Angenommen, Sie erstellen eine private Implementierung einer Schnittstelle, die über die ABI übergeben werden muss. In diesem Szenario ist die die Verwendung einer privaten Referenzklasse erforderlich. Dies ist jedoch eine Ausnahme.

Analog wird empfohlen, die Datentypen im Platform-Namespace wie Platform::String in erster Linie in öffentlichen Referenzklassen sowie für den Datenaustausch mit Windows-Runtime-Funktionen zu verwenden. Beim Durchlaufen der ABI können Sie std::wstring und wchar_t * in Ihrer App verwenden und in ein Platform::String^-Handle konvertieren.

Verwenden von modernem und standardisiertem C++ einschließlich des std-Namespaces

Windows Store-Apps sollten die aktuellsten Methoden, Standards und Bibliotheken für die Codierung in C++ verwenden. Programmieren Sie mit modernen Methoden, und verwenden Sie die Datentypvorgänge des std-Namespaces. Beispielsweise verwendet Hilo den Typ std::shared_ptr und den Typ std::vector.

C++/CX muss nur auf der äußersten Ebene Ihrer App verwendet werden. Diese Ebene interagiert über die ABI mit XAML und möglicherweise auch mit anderen Sprachen wie JavaScript oder C#.

Einheitliches Verwenden des Abbruchs von Aufgaben

Abbrüche können mit PPL-Aufgaben und IAsyncInfo-Objekten auf unterschiedliche Weise implementiert werden. Es empfiehlt sich daher, eine Implementierungsmöglichkeit auszuwählen und einheitlich in der gesamten App zu verwenden. Ein einheitlicher Ansatz zum Abbrechen von Aufgaben hilft, Codierungsfehler zu vermeiden und erleichtert die Überprüfung von Code. Diese Entscheidung ist sicherlich Ansichtssache, hat sich jedoch im Falle von Hilo bewährt.

  • Die Unterstützung für externe tokenbasierte Abbrüche wird jeweils für eine Fortsetzungskette und nicht für einzelne Aufgaben in einer Fortsetzungskette festgelegt. Wenn die erste Aufgabe in einer Fortsetzung den externen Abbruch mit einem Abbruchtoken unterstützt, muss der tokenbasierte Abbruch daher von allen Aufgaben in der Kette (einschließlich der geschachtelten Aufgaben) unterstützt werden. Wenn der tokenbasierte Abbruch nicht von der ersten Aufgabe unterstützt wird, wird dieser Vorgang auch von keiner weiteren Aufgabe in der Kette unterstützt.
  • Immer, wenn eine neue Fortsetzungskette erstellt wird, die Abbruchvorgänge unterstützen muss, die außerhalb der eigentlichen Fortsetzungskette initiiert werden, erstellen wir ein concurrency::cancellation_token_source-Objekt. So verwenden wir in Hilo Quellobjekte für Abbruchtoken, um das Abbrechen von ausstehenden asynchronen Vorgängen zu ermöglichen, wenn der Benutzer die aktuelle Seite verlässt.
  • Wir rufen die get_token-Methode der Quelle für das Abbruchtoken auf. Wir übergeben das Abbruchtoken als Argument an die create_task-Funktion, die die erste Aufgabe in der Fortsetzungskette erstellt. Bei geschachtelten Fortsetzungen wird das Abbruchtoken an die erste Aufgabe jeder geschachtelten Fortsetzungskette übergeben. An Aufrufe von task::then, die Fortsetzungen erstellen, wird kein Abbruchtoken übergeben. Wertbasierte Fortsetzungen erben standardmäßig das Abbruchtoken der vorhergehenden Aufgabe. Das Bereitstellen eines Abbruchtokenarguments ist in diesem Fall nicht erforderlich. Aufgabenbasierte Fortsetzungen erben keine Abbruchtoken. In aller Regel werden hier jedoch Bereinigungsvorgänge ausgeführt, die unabhängig von möglichen Abbrüchen erforderlich sind.
  • Wenn langfristige Aufgaben abgebrochen werden können rufen wir in regelmäßigen Abständen die concurrency::is_task_cancellation_requested-Funktion auf, um zu überprüfen, ob eine Anforderung für einen Abbruch aussteht. Im Anschluss an die Bereinigungsvorgänge rufen wir die concurrency::cancel_current_task-Funktion auf, um die Aufgabe zu beenden und den Abbruch in der Fortsetzungskette weiterzuleiten.
  • Wenn eine vorhergehende Aufgabe den Abbruch auf andere Art und Weise signalisiert, beispielsweise durch Zurückgeben von nullptr, rufen wir die cancel_current_task-Funktion in der Fortsetzung auf, um den Abbruch in der Fortsetzungskette weiterzuleiten. In einigen Fällen kann der Empfang eines Nullzeigers von der vorhergehenden Aufgabe die einzige Möglichkeit darstellen, eine Fortsetzungskette abzubrechen. Dies bedeutet, dass nicht alle Fortsetzungsketten von außen abgebrochen werden können. Beispielsweise verwenden wir in Hilo, in einigen Fällen eine Fortsetzungskette, wenn der Benutzer die Schaltfläche zum Abbrechen der Dateiauswahl betätigt. Der asynchrone Vorgang für die Dateiauswahl gibt dann nullptr. zurück.
  • Ein try/catch-Block wird in einer aufgabenbasierten Fortsetzung immer dann verwendet, wenn festgestellt werden muss, ob eine vorhergehende Aufgabe in einer Fortsetzungskette abgebrochen wurde. In dieser Fall fangen wir die concurrency::task_canceled-Ausnahme ab.
  • Das ObserveException-Funktionsobjekt von Hilo enthält einen try/catch-Block, der verhindert, dass eine task_canceled-Ausnahme unbemerkt bleibt. Dadurch wird verhindert, dass die App bei einem Abbruch beendet wird, wenn keine andere spezifische Ausnahmebehandlung vorhanden ist.
  • Wenn eine Aufgabe, die abgebrochen werden kann, von einer Hilo-Komponente für einen anderen Teil der App erstellt wird, stellen wir sicher, dass die Funktion, mit der die Aufgabe erstellt wird, ein cancellation_token als Argument akzeptiert. Dies ermöglicht dem Aufrufer das Angeben des Abbruchverhaltens.

Behandeln von Ausnahmen für Aufgaben mit einer aufgabenbasierten Fortsetzung

Am Ende jeder Fortsetzungskette sollte eine Fortsetzung stehen, die Ausnahmen mit einem finalen Lambdaausdruck abfängt. Es wird empfohlen, nur Ausnahmen abzufangen, bei denen bekannt ist, wie sie behandelt werden müssen. Andere Ausnahmen werden von der Laufzeit abgefangen und sorgen dafür, dass die App mit der Fail-Fast-Funktion std::terminate beendet wird.

Am Ende jeder Fortsetzungskette in Hilo steht eine Fortsetzung, die das Funktionsobjekt ObserveException von Hilo aufruft. Weitere Informationen sowie eine exemplarische Vorgehensweise mit Code finden Sie auf dieser Seite unter Verwenden von aufgabenbasierten Fortsetzungen zur Behandlung von Ausnahmen.

Lokales Behandeln von Ausnahmen bei Verwendung der when_all-Funktion

Die when_all-Funktion wird unmittelbar nach dem Auslösen einer Ausnahme durch eine der zugehörigen Aufgaben zurückgegeben. Einige Aufgaben werden zu diesem Zeitpunkt möglicherweise noch ausgeführt. Wenn eine Ausnahme auftritt, muss der Handler auf die ausstehenden Aufgaben warten.

Um keine Test auf Ausnahmen durchführen und nicht auf den Abschluss von Aufgaben warten zu müssen, wird empfohlen, Ausnahmen lokal in den Aufgaben zu behandeln, die von when_all verwaltet werden.

Beispielsweise lädt Hilo Miniaturbilder mit when_all. Ausnahmen werden in den Aufgaben behandelt und nicht an when_all zurückgegeben. Hilo übergibt ein konfigurierbares Richtlinienobjekt für Ausnahmen an die Aufgaben, um sicherzustellen, dass diese die globale Ausnahmenrichtlinie der App verwenden können. Hier ist der Code.

ThumbnailGenerator.cpp


task<Vector<StorageFile^>^> ThumbnailGenerator::Generate( 
    IVector<StorageFile^>^ files, 
    StorageFolder^ thumbnailsFolder)
{
    vector<task<StorageFile^>> thumbnailTasks;

    unsigned int imageCounter = 0;
    for (auto imageFile : files)
    {
        wstringstream localFileName;
        localFileName << ThumbnailImagePrefix << imageCounter++ << ".jpg";

        thumbnailTasks.push_back(
            CreateLocalThumbnailAsync(
            thumbnailsFolder,
            imageFile, 
            ref new String(localFileName.str().c_str()),
            ThumbnailSize,
            m_exceptionPolicy));
    }

    return when_all(begin(thumbnailTasks), end(thumbnailTasks)).then(
        [](vector<StorageFile^> files)
    {
        auto result = ref new Vector<StorageFile^>();
        for (auto file : files)
        {
            if (file != nullptr)
            {
                result->Append(file);
            }
        }

        return result;
    });
}


Hier sehen Sie den Code für die einzelnen Aufgaben.

ThumbnailGenerator.cpp


task<StorageFile^> ThumbnailGenerator::CreateLocalThumbnailAsync(
    StorageFolder^ folder,
    StorageFile^ imageFile,
    String^ localFileName,
    unsigned int thumbSize,
    std::shared_ptr<ExceptionPolicy> exceptionPolicy)
{
    auto createThumbnail = create_task(
        CreateThumbnailFromPictureFileAsync(imageFile, thumbSize));

    return createThumbnail.then([exceptionPolicy, folder, localFileName](
        task<InMemoryRandomAccessStream^> createdThumbnailTask) 
    {
        InMemoryRandomAccessStream^ createdThumbnail;
        try 
        {
            createdThumbnail = createdThumbnailTask.get();
        }
        catch(Exception^ ex)
        {
            exceptionPolicy->HandleException(ex);
            // If we have any exceptions we won't return the results
            // of this task, but instead nullptr.  Downstream 
            // tasks will need to account for this.
            return create_task_from_result<StorageFile^>(nullptr);
        }

        return InternalSaveToFile(folder, createdThumbnail, localFileName);
    });
}


Aufrufen der Ansichtsmodellobjekte nur aus dem Hauptthread

Einige Methoden und Eigenschaften von Ansichtsmodellobjekten werden mit Datenbindungen an XAML-Steuerelemente gebunden. Wenn diese Methoden und Eigenschaften von der Benutzeroberfläche aufgerufen werden, wird der Hauptthread verwendet. Üblicherweise werden auch alle Methoden und Eigenschaften von Ansichtsmodellobjekten im Hauptthread aufgerufen.

Verwenden von Hintergrundthreads nach Möglichkeit

Wenn die erste Aufgabe in einer Fortsetzungskette einen asynchronen Windows-Runtime-Vorgang einschließt, werden die Fortsetzungen standardmäßig im gleichen Thread wie die task::then-Methode ausgeführt. Dies ist i. d. R. der Hauptthread. Diese Standardeinstellung eignet sich für Vorgänge, die mit XAML-Steuerelementen interagieren. Sie eignet sich jedoch nicht für zahlreiche App-Spezifische Verarbeitungsvorgänge wie die Anwendung von Bildfiltern oder die Durchführung anderer rechenintensiver Vorgänge.

Verwenden Sie den von der task_continuation_context::use_arbitrary -Funktion zurückgegebenen Wert als Argument für die task::then-Methode, wenn Sie Fortsetzungen erstellen, die im Hintergrund in Threadpoolthreads ausgeführt werden sollen.

Rufen Sie keine Sperrvorgänge aus dem Hauptthread auf.

Rufen Sie keine langfristigen Vorgänge im Hauptthread auf. Als Faustregel kann angenommen werden, dass keine vom Benutzer erstellten Funktionen aufgerufen werden sollten, durch die der Hauptthread länger als 50 Millisekunden blockiert wird. Wenn Sie eine langfristige Funktion aufrufen müssen, sollten Sie diese in eine Aufgabe einschließen, die im Hintergrund ausgeführt wird.

Hinweis  Dieser Hinweis bezieht sich nur auf die Zeit, in der der Hauptthread blockiert ist, nicht auf die Zeit, die erforderlich ist, um einen asynchronen Vorgang abzuschließen. Bei asynchronen Vorgängen, deren Abschluss viel Zeit in Anspruch nimmt, wird empfohlen, einen visuellen Hinweis zum Status anzuzeigen und darauf zu achten, dass der Hauptthread nicht blockiert wird.
 

Weitere Informationen finden Sie unter Sicherstellen eines reaktionsfähigen UI-Threads (Windows Store-Apps mit C#/VB/C++ und XAML).

Rufen Sie task::wait nicht aus dem Hauptthread auf.

Rufen Sie die task::wait-Methode nicht aus dem Hauptthread (oder einem anderen STA-Thread) auf. Wenn Sie nach Abschluss der PPL-Aufgabe eine Aktion im Hauptthread ausführen müssen, können Sie mit der task::then-Methode eine Fortsetzungsaufgabe erstellen.

Beachten Sie die speziellen Kontextregeln für Fortsetzungen von Aufgaben zum Einschließen asynchroner Objekte.

Windows Store-Apps mit C++ verwenden den Compilerschalter /ZW. Dieser Schalter wirkt sich auf das Verhalten verschiedener Funktionen aus. Wenn Sie diese Funktionen für Desktop- oder Konsolenanwendungen verwenden, sollten Sie daher darauf achten, welche Auswirkungen die Verwendung des Schalters hat.

Mit dem /ZW-Schalter wird der aktuelle Laufzeitkontext der Fortsetzung beim Aufrufen der task::then-Methode für eine Aufgabe, die ein IAsyncInfo-Objekt einschließt, als aktueller Kontext verwendet Der aktuelle Kontext ist der Thread, mit dem die then-Methode aufgerufen wurde. Diese Standardeinstellung betrifft alle Fortsetzungen in einer Fortsetzungskette, nicht nur die erste. Diese besondere Standardverhalten erleichtert die Codeerstellung für Windows Store-Apps. Fortsetzungen von Windows-Runtime-Vorgängen müssen i. d. R. mit Objekten interagieren, die eine Ausführung aus dem Hauptthread der App erfordern. Ein Beispiel hierzu finden Sie auf dieser Seite unter Beispiele zur Verwendung des Fortsetzungskettenmusters.

Wenn eine Aufgabe keine der IAsyncInfo-Schnittstellen einschließt, erzeugt die then-Methode eine Fortsetzung. Diese wird standardmäßig in einem Thread ausgeführt, der vom System aus dem Threadpool ausgewählt wird.

Sie können das Standardverhalten durch Übergeben eines zusätzlichen Kontextparameters an die task::then-Methode überschreiben. Wenn die erste Aufgabe der Fortsetzungskette von einer separaten Komponente oder einer Hilfsmethode der aktuellen Komponente erstellt wurde, wird dieser optionale Parameter in Hilo immer bereitgestellt.

Hinweis  In Hilo erschien uns eine Klärung im Hinblick auf den Threadkontext für die untergeordneten Routinen mit der assert(IsMainThread()) - und der assert(IsBackgroundThread())-Anweisung angebracht. In der Debugversion von Hilo lösen diese Anweisungen eine Ausnahme aus, wenn ein anderer Thread als der in der Assertion deklarierte verwendet wird. Die Implementierungen der IsMainThread- und der IsBackgroundThread-Funktion erfolgen in der Hilo-Quelle.
 

Beachten Sie die speziellen Kontextregeln für die create_async-Funktion.

Mit dem /ZW-Schalter stellt die Headerdatei "ppltasks.h" die concurrency::create_async-Funktion bereit. Mit dieser Funktion können Sie Aufgaben erstellen, die automatisch von einer IAsyncInfo-Schnittstelle der Windows-Runtime eingeschlossen werden. Die create_task- und create_async-Funktion weisen eine grundsätzliche eine ähnliche Syntax und ein ähnliches Verhalten auf. In einigen Punkten unterscheiden sich beide jedoch.

Wenn Sie eine Arbeitsfunktion als Argument an die create_async-Funktion übergeben, kann diese void, einen gewöhnlichen T-Typ oder task<T> zurückgeben, oder die Funktion kann alle von IAsyncInfo abgeleiteten Schnittstellen wie IAsyncAction^ und IAsyncOperation<T>^ verarbeiten.

Wenn Sie die create_async-Funktion aufrufen, wird die Arbeitsfunktion synchron im aktuellen Kontext oder asynchron im Hintergrund ausgeführt. Dies ist abhängig davon, welchen Rückgabetyp die Arbeitsfunktion verwendet. Wen der Rückgabetyp der Arbeitsfunktion ungültig oder ein herkömmlicher Typ T ist, wird die Arbeitsfunktion im Hintergrund in einem Threadpoolthread ausgeführt. Wenn der Rückgabetyp der Arbeitsfunktion eine Aufgabe oder ein Handle für eine Schnittstelle ist, die von IAsyncInfo abgleitet wird, wird die Arbeitsfunktion synchron im aktuellen Thread ausgeführt. Das synchrone Ausführen der Arbeitsfunktion ist hilfreich bei kleinen Funktionen mit geringem Einrichtungsaufwand vor dem Aufrufen einer anderen asynchronen Funktion.

Hinweis  Im Gegensatz zur create_async-Funktion wird die create_task-Funktion beim Aufrufen immer im Hintergrund in einem Threadpoolthread ausgeführt. Dies gilt unabhängig vom Rückgabetyp der Arbeitsfunktion.
 
Hinweis  In Hilo erschien uns eine Klärung im Hinblick auf den Threadkontext für die untergeordneten Routinen mit der assert(IsMainThread()) - und der assert(IsBackgroundThread())-Anweisung angebracht. In der Debugversion von Hilo lösen diese Anweisungen eine Ausnahme aus, wenn ein anderer Thread als der in der Assertion deklarierte verwendet wird. Die Implementierungen der IsMainThread- und der IsBackgroundThread-Funktion erfolgen in der Hilo-Quelle.
 

Beachten Sie mögliche Anforderungen für App-Container bei der parallelen Programmierung.

Sie können die PPL und die asynchrone Agents-Bibliothek in einer Windows Store-App verwenden. Die Verwendung der Aufgabenplanung für die Konkurrenz-Runtime oder des Ressourcen-Managers ist jedoch nicht möglich.

Verwenden Sie explizite Erfassungen für Lambdaausdrücke.

Die Variablen, die in Lambdaausdrücken erfasst werden, sollten möglichst explizit angegeben werden. Daher wird von der Verwendung der Option "[=]" und der Option "[&]" in Lambdaausdrücken abgeraten.

Beachten Sie beim Erfassen von Variablen in Lambda-Ausdrücken außerdem die Objektlebensdauer. Variablen mit stapelzugeordneten Objekten sollten nicht über Verweise erfasst werden, da andernfalls Speicherfehler auftreten können. Membervariablen von kurzlebigen Klassen sollten aus dem gleichen Grund nicht erfasst werden.

Erstellen Sie keine Zirkelverweise zwischen Referenzklassen und Lamdaausdrücken.

Wenn Sie einen Lambdaausdruck erstellen und den this-Handle mit dem Wert erfassen, wird die Verweiszählung des this-Handles inkrementiert. Wenn Sie den neu erstellten Lambdaausdruck später an eine Membervariable der Klasse binden, auf die von this verwiesen wird erstellen Sie einen Zirkelverweis. Dies kann einen Speicherverlust zur Folge haben.

Verwenden Sie schwache Verweise, um den this-Verweis zu erfassen, wenn der resultierende Lambdaausdruck einen Zirkelverweis erstellen würde. Ein Codebeispiel finden Sie unter Schwache Verweise und unterbrochene Zyklen (C++/CX).

Verzichten auf unnötige Synchronisierungen

Die Art und Weise, in der Windows Store-Apps programmiert werden, kann manche Formen der Synchronisierung wie Sperren weniger oft erforderlich machen. Wenn mehrere Fortsetzungsketten gleichzeitig ausgeführt werden, können sie ganz einfach den Hauptthread als Synchronisierungspunkt verwenden. Beispielsweise erfolgt der Zugriff auf die Membervariablen von Ansichtsmodellobjekten auch in hochgradig asynchronen Apps stets über den Hauptthread. Eine Synchronisierung mit Sperren ist daher überflüssig.

In Hilo wird genau darauf geachtet, dass der Zugriff die Membervariablen von Ansichtsmodellklassen nur über den Hauptthread erfolgt.

Adäquates Differenzieren hinsichtlich der Parallelität

Verwenden Sie gleichzeitig ablaufende Vorgänge nur für langfristige Aufgaben. Die Einrichtung eines asynchronen Aufrufs ist mit einem gewissen zusätzlichen Aufwand verbunden.

Beachten möglicher Interaktionen zwischen Abbrüchen und Ausnahmebehandlungen

PPL implementiert die Funktion zum Abbrechen der Aufgabe teilweise durch die Verwendung von Ausnahmen. Wenn Sie unbekannte Ausnahmen abfangen, kann es zu Problemen mit dem Abbruchmechanismus von PPL kommen.

Hinweis  Wenn Sie den Abbruch nur mit entsprechenden Token signalisieren und nicht die cancel_current_task-Funktion aufrufen, werden keine Ausnahmen für den Abbruch verwendet.
 
Hinweis  Wenn eine Fortsetzung nach Abbrüchen oder Ausnahmen suchen muss, die in vorhergehenden Aufgaben in der Fortsetzungskette aufgetreten sind, sollten Sie sich vergewissern, dass es sich um eine aufgabenbasierte Fortsetzung handelt.
 

Verwenden paralleler Muster

Wenn Ihre App rechenintensive Vorgänge ausführt, ist die Wahrscheinlichkeit sehr hoch, dass Sie parallele Programmiermethoden verwenden müssen. Es gibt eine Reihe von bewährten Mustern für die effiziente Nutzung von Hardware mit Mehrkernprozessor. Unter Parallele Programmierung mit Microsoft Visual C++ finden Sie die gängigsten Muster sowie Beispiele mit PPL und der asynchronen Agents- Bibliothek.

Beachten spezieller Testanforderungen asynchroner Vorgänge

Einheitentests erfordern einen besonderen Umgang mit der Synchronisierung, wenn asynchrone Aufrufe vorhanden sind. In den meisten Testumgebungen ist das warten auf asynchrone Ergebnisse nicht zulässig. Dieses Problem kann nur durch spezielle Codierung umgangen werden.

Wenn Sie Komponenten testen, bei denen einige Vorgänge im Hauptthread ausgeführt werden müssen, muss darüber hinaus sichergestellt werden können, dass das Testframework im erforderlichen Threadkontext ausgeführt werden kann.

In Hilo wurden diese Probleme mithilfe von benutzerdefiniertem Testcode gelöst. Nähere Informationen hierzu finden Sie im vorliegenden Handbuch unter Testen der App.

Verwalten von Interleavvorgängen mit finiten Statuscomputern

Asynchrone Programme weisen eine höhere Anzahl an möglichen Ausführungspfaden und Funktionsinteraktionen als synchrone Programme auf. Angenommen, Ihre App ermöglichen Benutzern das Betätigen der Zurück-Schaltfläche in einem asynchronen Vorgang (z. B. Laden von Dateien). In diesem Fall müssen Sie entsprechende Vorkehrungen in der Logik Ihrer App treffen. Sie können den ausstehenden Vorgang abbrechen und den Zustand der App vor Beginn der Ausführung des Vorgangs wiederherstellen.

Durch die flexible Überlappung der Vorgänge Ihrer App erhalten Benutzer einen besseren Eindruck von der Geschwindigkeit und Reaktionsfähigkeit Ihrer App. Dies ist eine wichtige Eigenschaft von Windows Store-Apps. Gleichzeitig kann mangelnde Sorgfalt an dieser Stelle aber auch eine Fehlerquelle darstellen.

Der ordnungsgemäße Umgang mit den zahlreichen möglichen Ausführungspfaden und Interaktionen in einer asynchronen Benutzeroberfläche erfordert spezielle Programmiermethoden. Finite Statuscomputer stellen hier eine gute Wahl dar. Sie können helfen, eine robuste Behandlung für überlappende asynchrone Vorgänge zu implementieren. Mithilfe eines finiten Statuscomputers können Sie explizit angeben, was in jeder möglichen Situation geschehen soll.

Hier ist das Diagramm eines finiten Statuscomputers aus dem Bildbrowser-Ansichtsmodell von Hilo.

Bildbrowser-Ansichtsmodell für finiten Statuscomputer

Jedes Oval im Diagramm stellt einen anderen Betriebsmodus dar, den eine Instanz der ImageBrowserViewModel-Klasse zur Laufzeit aufweisen kann. Die Bögen stellen benannte Übergänge zwischen einzelnen Modi dar. Einige Zustandsübergänge erfolgen nach Abschluss eines asynchronen Vorgangs. Sie erkennen diese Vorgänge an der entsprechenden Bezeichnung im Namen ("Finish"). Alle anderen Übergänge erfolgen zu Beginn eines asynchronen Vorgangs.

Im Diagramm sehen Sie die möglichen Vorgänge in den einzelnen Betriebsmodi. Wenn der Bildbrowser sich beispielsweise im Running-Modus befindet und eine OnNavigatedFrom-Aktion ausgeführt wird, lautet der resultierende Modus Pending. Übergänge sind das Ergebnis folgender Vorgänge:

  • Benutzeraktionen (z. B. Navigationsanforderungen)
  • Programmaktionen (z. B. der Abschluss eines zuvor gestarteten asynchronen Vorgangs)
  • Externe Ereignisse (z. B. Benachrichtigungen über Netzwerk- oder Dateisystemereignisse)

Manche Übergänge sind in einigen Modi nicht verfügbar. Beispielsweise kann der Übergang FinishMonthAndYearQueries nur im Modus Running erfolgen.

Im Code stellen die benannten Übergänge Memberfunktionen der ImageBrowserViewModel-Klasse dar. Der aktuelle Betriebsmodus des Bildbrowsers ist der Wert der m_currentMode-Membervariablen. In dieser Variablen werden die Werte der ImageBrowserViewModel::Mode-Enumeration gespeichert.

ImageBrowserViewModel.h


enum class Mode {
    Default,        /* (0, 0, 0): no pending data changes, not updating, not visible */
    Active,         /* (0, 0, 1): no pending data changes, not updating, visible */
    Pending,        /* (1, 0, 0): pending data changes, not updating, not visible */
    Running,        /* (0, 1, 1): no pending data changes, updating, visible  */    
    NotAllowed      /* error state */
};


Hier ist ein Beispiel für den Code zur Implementierung des ObserveFileChange-Übergangs im Diagramm.

ImageBrowserViewModel.cpp


// State transition occurs when file system changes invalidate the result of the current query.
void ImageBrowserViewModel::ObserveFileChange()
{
    assert(IsMainThread());

    switch (m_currentMode)
    {
    case Mode::Default:
        m_currentMode = Mode::Pending;
        break;

    case Mode::Pending:
        m_currentMode = Mode::Pending;
        break;

    case Mode::Active:
        StartMonthAndYearQueries();
        m_currentMode = Mode::Running;
        break;

    case Mode::Running:
        CancelMonthAndYearQueries();
        StartMonthAndYearQueries();
        m_currentMode = Mode::Running;
        break;
    }
}


Die ObserveFileChange-Methode reagiert auf Benachrichtigungen des Betriebssystems über externe Änderungen an den Bilddateien, die vom Benutzer betrachtet werden. Je nachdem, welche Aktionen in der App zum Zeitpunkt der Benachrichtigung ausgeführt werden, sind 4 Fälle zu berücksichtigen. Wenn derzeit eine asynchrone Abfrage vom Bildbrowser ausgeführt wird, lautet der entsprechende Modus Running. In diesem Modus wird die Ausführung der Abfrage von der ObserveFileChange-Aktion abgebrochen, da das Ergebnis nicht mehr benötigt wird, und eine neue Abfrage wird gestartet. Die Betriebsmodus lautet weiterhin Running.

[Nach oben]

 

 

Anzeigen:
© 2016 Microsoft