Modelli di programmazione asincrona e suggerimenti in Hilo (app per Windows Store scritte in C++ e XAML)

Applies to Windows only

Da : Sviluppo di un'app end-to-end per Windows Store scritta in C++ e XAML: Hilo

Logo Patterns & Practices

Pagina precedente | Pagina successiva

Hilo contiene molti esempi di catene di continuazione in C++, un modello di programmazione asincrona molto usato per le app di Windows Store. Ecco qualche suggerimento e qualche indicazione sull'utilizzo delle catene di continuazione, oltre a qualche esempio dei vari modi in cui puoi utilizzarle nelle app.

Download

Scarica una copia di esempio di Hilo
Download guida (PDF)

Dopo aver scaricato il codice, per le istruzioni vedi Introduzione a Hilo.

Contenuti

  • Procedure consigliate per la programmazione asincrona in Windows Store con C++.
  • Annullamento delle operazioni asincrone in sospeso
  • Gestione delle eccezioni che si verificano nelle operazioni asincrone.
  • Uso delle attività in parallelo con le app di Windows Store scritte in Visual C++.

Si applica a

  • Windows Runtime per Windows 8
  • Estensioni del componente Visual C++ (C++/CX)
  • Libreria PPL (Parallel Patterns Library)

Esempi di utilizzo delle catene di continuazione

Le tecniche di programmazione asincrona che utilizziamo in Hilo rientrano nel modello generale denominato catena di continuazione o .then ladder (pronuncia: dot-then ladder). Una catena di continuazione è una sequenza di attività PPL collegate da relazioni nel flusso di dati. L'output di ogni attività diventa l'input della continuazione successiva nella catena. L'inizio della catena è in genere un'attività che esegue il wrapping di un'operazione asincrona di Windows Runtime (un'interfaccia che deriva da IAsyncInfo). Abbiamo presentato le catene di continuazione nell'argomento della guida su scrittura di codice C++ modern per le app di Windows Store.

Ecco un esempio realizzato in Hilo che mostra una catena di continuazione dove all'inizio c'è un'attività che esegue il wrapping di un'interfaccia derivante da IAsyncInfo.

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


In questo esempio, il contesto di chiamata previsto del metodo FileSystemRepository::GetSinglePhotoAsync è il thread principale. Il metodo crea un'attività PPL che esegue il wrapping del risultato del metodo GetFilesAsync. Il metodo GetFilesAsync è una funzione di supporto che restituisce un handle IAsyncOperation< IVectorView<StorageFile^>^>^.

Il metodo task::then crea continuazioni eseguite sullo stesso thread o su un thread diverso da quello dell'attività che le precede, a seconda di come le configuri. Questo esempio mostra una continuazione.

Nota   Poiché l'attività iniziale creata dalla funzione concurrency::create_task esegue il wrapping di un tipo derivato da IAsyncInfo, per impostazione predefinita le relative continuazioni vengono eseguite nel contesto che crea la catena di continuazione, che in questo esempio è costituita dal thread principale.

Alcune operazioni, come quelle che interagiscono con i controlli XAML, devono verificarsi nel thread principale, Ad esempio, l'oggetto PhotoImage può essere associato a un controllo XAML e pertanto le sue istanze devono essere create nel thread principale, mentre tu, d'altro canto, devi chiamare le operazioni di blocco esclusivamente da un thread in background e non dal thread principale dell'app. Perciò, per evitare errori di programmazione, devi sapere se le singole continuazioni utilizzeranno il thread principale o un thread in background del pool di thread.

Esistono vari modi per utilizzare il modello della catena di continuazione, a seconda della situazione. Ecco alcune variazioni del modello della catena di continuazione di base:

Continuazioni basate sul valore e su attività.

L'attività che precede una continuazione è denominata attività antecedente o antecedente la continuazione. Se un'attività antecedente è di tipo task<T>, la continuazione di quell'attività potrà accettare come tipo di argomento il tipo T o il tipo task<T>. Le continuazioni che accettano il tipo T sono continuazioni basate sul valore. Le continuazioni che accettano il tipo task<T> sono continuazioni basate su attività.

L'argomento di una continuazione basata sul valore è il valore restituito dalla funzione di lavoro dell'attività antecedente la continuazione. L'argomento di una continuazione basata su attività è l'attività antecedente stessa. Per ricercare l'output dell'attività antecedente puoi usare il metodo task::get.

Esistono varie differenze di comportamento tra le continuazioni basate sul valore e quelle basate su attività. Le continuazioni basate sul valore non vengono eseguite se l'attività antecedente è terminata in un'eccezione. Per contro, le continuazioni basate su attività vengono eseguite a prescindere dallo stato di eccezione dell'attività antecedente. Inoltre, le continuazioni basate sul valore ereditano per impostazione predefinita il token di annullamento dell'antecedente, mentre le continuazioni basate su attività no.

Le continuazioni sono per la maggior parte basate sul valore. Le continuazioni basate su attività si usano soprattutto negli scenari che prevedono gestione delle eccezioni e annullamento. Se desideri qualche esempio, vedi Consentire l'annullamento delle catene di continuazione dall'esterno e Uso delle continuazioni basate su attività per la gestione delle eccezioni più avanti in questo argomento.

Attività con rimozione del wrapping.

Nella maggior parte dei casi la funzione di lavoro di un'attività PPL restituisce il tipo T se l'attività è di tipo task<T>, ma la restituzione del tipo T non è l'unica possibilità. È possibile anche la restituzione di un valore task<T> da parte della funzione di lavoro passata alla funzione create_task o task::then. In questo caso, potresti pensare che la libreria PPL crei un'attività di tipo task<task<T>>, ma non è così. Infatti, l'attività risultante è di tipo task<T>. La trasformazione automatica di task<task<T>> in task<T> è denominata rimozione del wrapping di un'attività.

La rimozione del wrapping si verifica a livello del tipo. La libreria PPL pianifica l'attività interna e utilizza il risultato ottenuto come risultato dell'attività esterna. L'attività esterna viene completata quando quelle interna porta a termine il lavoro.

Attività con rimozione del wrapping.

L'attività 1 dello schema presenta una funzione di lavoro che restituisce il valore int. L'attività 1 è di tipo task<int>. L'attività 2 presenta una funzione di lavoro che restituisce il valore task<int>, che corrisponde all'attività 2a del diagramma. L'attività 2 attende il completamento dell'attività 2a, quindi restituisce il risultato della funzione di lavoro dell'attività 2a come risultato dell'attività 2.

L'eliminazione del wrapping delle attività facilita l'inserimento di logica condizionale per il flusso dei dati in reti di attività correlate. Per scenari ed esempi che richiedono la rimozione del wrapping delle attività da parte della libreria PPL, vedi Uso delle continuazioni nidificate per la logica condizionale più avanti in questa pagina. Per visualizzare una tabella dei tipi restituiti vedi "Tipi restituiti della funzione lambda e tipi restituiti dell'attività" in Programmazione asincrona in C++.

Consentire l'annullamento delle catene di continuazione dall'esterno

Puoi crea catene di continuazione che rispondano alle richieste di annullamento esterne. Ad esempio, quando visualizzi in Hilo la pagina del visualizzatore immagini, la pagina avvia un'operazione asincrona che crea e visualizza gruppi di foto per ogni mese. Se lasci la pagina prima che l'operazione di visualizzazione si concluda, l'operazione in sospeso che crea i controlli del gruppo mensile viene annullata. Questa è la procedura:

  1. Crea un oggetto concurrency::cancellation_token_source.
  2. Esegui una query sul token di annullamento per ottenere un concurrency::cancellation_token usando il metodo cancellation_token_source::get_token.
  3. Passa come argomento il token di annullamento alla funzione concurrency::create_task dell'attività iniziale della catena di continuazione o al metodo task::then di qualsiasi continuazione. Questa operazione è necessaria una sola volta per ogni catena di continuazione, perché per impostazione predefinita le attività di continuazione successive usano il contesto di annullamento dell'attività antecedente.
  4. Per annullare la catena di continuazione mentre è in esecuzione, chiama il metodo cancel dell'origine del token di annullamento da qualsiasi contesto del thread.

Ecco un esempio.

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

Il metodo StartMonthAndYearQueries crea gli oggetti concurrency::cancellation_token_source e concurrency::cancellation_token e passa il token di annullamento ai metodi StartMonthQuery e StartYearQuery. Ecco l'implementazione del metodo StartMonthQuery.

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


Il metodo StartMonthQuery crea una catena di continuazione. L'intestazione della catena di continuazione e la prima attività i continuazione della catena vengono costruiti dal metodo GetMonthGroupedPhotosWithCacheAsync della classe FileSystemRepository.

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

Questo codice passa un token di annullamento al metodo task::then alla prima attività di continuazione.

L'esecuzione delle query relative all'anno e al mese possono richiedere alcuni secondi se le immagini da elaborare sono molte. Durante l'esecuzione delle query l'utente può uscire dalla pagina. In questo caso le query vengono annullate. Il metodo OnNavigatedFrom nell'app Hilo chiama il metodo CancelMonthAndYearQueries. Il metodo CancelMonthAndYearQueries della classe ImageBrowserViewModel richiama il metodo cancel dell'origine del token di annullamento.

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

Nella libreria PPL l'annullamento è cooperativo. Puoi controllare il comportamento di annullamento delle attività che implementi. Per informazioni dettagliate su ciò che si verifica quando chiami il metodo di annullamento dell'origine del token di annullamento vedi "Annullamento di attività" in Programmazione asincrona in C++.

Altre modalità di segnalazione dell'annullamento.

Una continuazione basata sul valore non si avvia se è stato chiamato il metodo cancel dell'origine del token di annullamento a essa associata. È inoltre possibile che il metodo cancel venga chiamato durante l'esecuzione di un'attività all'interno di una catena di continuazione. Se ti interessa sapere se una richiesta di annullamento esterna è stata segnalata durante l'esecuzione di un'attività, per scoprirlo puoi chiamare la funzione concurrency::is_task_cancellation_requested. La funzione is_task_cancellation_requested verifica lo stato del token di annullamento dell'attività corrente. La funzione restituisce true se è stato chiamato il metodo cancel sull'oggetto origine del token di annullamento che ha creato il token di annullamento. Per visualizzare un esempio vedi l'argomento sull'uso di C++ nell'esempio di ottimizzazione di viaggi per Bing Mappe.

Nota  Puoi chiamare is_task_cancellation_requested nel corpo della funzione di lavoro per qualsiasi tipo di attività, incluse quelle create dalle funzioni create_task e create_async e dal metodo task::then.

Suggerimento  Non chiamare is_task_cancellation_requested in ogni iterazione di ciclo breve. In generale, se desideri rilevare una richiesta di annullamento in un'attività in esecuzione, esegui il polling del token di annullamento con una frequenza sufficiente a ottenere il tempo di risposta desiderato senza influire sulle prestazioni dell'app quando l'annullamento non è richiesto.

Se rilevi che un'attività in esecuzione deve elaborare una richiesta di annullamento, effettua tutte le operazioni di pulizia di cui ha bisogno l'app, quindi chiama la funzione concurrency::cancel_current_task in modo che ponga fine all'attività e notifichi al momento del runtime che c'è stato un annullamento.

I token di annullamento sono l'unico modo per segnalare le richieste di annullamento. Quando chiami funzioni libreria asincrone, a volte ricevi dall'attività antecedente un valore segnale speciale, ad esempio nullptr, che indica che è stato richiesto un annullamento.

Annullamento delle operazioni asincrone il cui wrapping è eseguito dalle attività.

Le attività PPL ti consentono di supportare l'annullamento nelle app che utilizzano le operazioni asincrone di Windows Runtime. Quando esegui il wrapping di un'interfaccia derivata da IAsyncInfo in un'attività, la libreria PPL avvisa l'operazione asincrona chiamando il metodo IAsyncInfo::Cancel se arriva una richiesta di annullamento.

Nota  La PPL concurrency::cancellation_token è di tipo C++ e non può superare il limite ABI. Ciò significa che non puoi passarla come argomento a un metodo pubblico di una classe ref pubblica. Se in questa situazione ti serve un token di annullamento, esegui il wrapping della chiamata al metodo pubblico asincrono della classe ref public in un'attività PPL che usa un token di annullamento. Quindi, chiama la funzione concurrency::is_task_cancellation_requested dall'interno della funzione di lavoro della funzione create_async interna al metodo asincrono. La funzione is_task_cancellation_requested ha accesso al token dell'attività PPL e restituisce true se il token è stato segnalato.

Uso delle continuazioni basate su attività per la gestione delle eccezioni

Le attività PPL supportano la gestione delle eccezioni differite. Puoi utilizzare una continuazione basata su attività per rilevare le eccezioni che si sono verificate in qualsiasi passaggio di una catena di continuazione.

In Hilo ricerchiamo sistematicamente le eccezioni al termine di ogni catena di continuazione. Per facilitare questa operazione, abbiamo creato una classe helper che ricerca le eccezioni e le gestisce in base a un criterio configurabile. Ecco il codice.

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


Assemblaggio degli output di più continuazioni

Le catene di continuazione usano uno stile di programmazione a flusso di dati dove il valore restituito di un'attività antecedente diventa l'input di un'attività di continuazione. In alcune situazioni il valore restituito dell'attività antecedente non contiene tutti gli elementi necessari per il passaggio successivo. Ad esempio, potrebbe essere necessario unire i risultati di due attività di continuazione prima che sia possibile passare alla terza continuazione. Esistono varie tecniche per gestire casi simili. In Hilo usiamo un puntatore condiviso (std::shared_ptr) per trattenere temporaneamente i valori intermedi sull'heap.

Ad esempio, lo schema che segue mostra le relazioni tra flusso di dati e flusso di controllo del metodo ThumbnailGenerator::CreateThumbnailFromPictureFileAsync di Hilo, che usa una catena di continuazione per creare immagini di anteprima. La creazione di un'anteprima da un file di immagine richiede passaggi asincroni non inseribili in un flusso di dati ad andamento rettilineo

Nello schema, le cornici non tratteggiare rappresentano altrettante operazioni asincrone in Windows Runtime, mentre le cornici tratteggiate rappresentano attività che chiamano le funzioni sincrone. Gli archi rappresentano input e output. Le frecce tratteggiate sono dipendenze del flusso di controllo per le operazioni aventi effetti collaterali. I numeri mostrano l'ordine in cui si verificano le operazioni nella catena di continuazione del metodo CreateThumbnailFromPictureFileAsync.

Metodo CreateThumbnailFromPictureFileAsync

Lo schema mostra interazioni troppo complesse per un flusso di dati lineare. Ad esempio, l'operazione sincrona "Imposta dati pixel" richiede l'input di tre operazioni asincrone distinte e modifica uno degli input, causando un vincolo di sequenza per un'operazione di cancellazione asincrona successiva. Ecco il codice di questo esempio.

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


L'esempio chiama la funzione std::make_shared per creare contenitori condivisi per handle dell'oggetto decoder e dell'oggetto provider di dati pixel che verranno creati dalle continuazioni. L'operatore * (di dereferenziazione) consente alle continuazioni di impostare i contenitori condivisi e di leggerne i valori.

Uso delle continuazioni nidificate per la logica condizionale

Le continuazioni nidificate consentono di differire la creazione di parti di una catena di continuazione fino alla fase di esecuzione. Le continuazioni nidificate risultano utili quando la catena presenta attività condizionali e si devono acquisire variabili locali delle attività di una catena di continuazione da utilizzare nelle attività di continuazione successive. Le continuazioni nidificate si basano sulla rimozione del wrapping delle attività.

L'operazione di rotazione immagini di Hilo si serve delle continuazioni nidificate per gestire la logica condizionale. L'operazione di rotazione si comporta in modo diverso a seconda che il formato file dell'immagine da ruotare supporti o meno la proprietà di orientamento Exchangeable Image File Format (EXIF). Ecco il codice.

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


Visualizzazione dello stato da un'operazione asincrona

Anche se l'app di Hilo non indica con una barra di stato lo stato delle operazioni in corso, forse tu desideri che la tua app lo faccia. Per altre informazioni, vedi il post di blog Keeping apps fast and fluid with asynchrony in the Windows Runtime, contenente alcuni esempi di utilizzo delle interfacce IAsyncActionWithProgress<TProgress> e IAsyncOperationWithProgress<TProgress, TResult>.

Nota  Durante le operazioni asincrone di lunga durata, Hilo visualizza un controllo anello di stato animato. Per altre info sulle modalità di visualizzazione dello stato in Hilo, vedi ProgressRing in questa guida.

Creazione di attività in background con create_async per scenari di interoperabilità

A volte, nelle classi che definisci sei costretto a usare direttamente le interfacce derivate da IAsyncInfo. Ad esempio, le firme dei metodi pubblici delle classi ref pubbliche dell'app non possono includere tipi non ref come task<T>. Invece, devi esporre le operazioni asincrone con una delle interfacce derivanti dall'interfaccia IAsyncInfo. Una classe ref pubblica è una classe C++ dichiarata con visibilità pubblica e la parola chiave ref C++/CX.

Puoi usare la funzione concurrency::create_async per esporre un'attività PPL come oggetto IAsyncInfo.

Per altre info sulla funzione create_async, vedi Creazione di operazioni asincrone in C++ per app di Windows Store.

Invio di funzioni al thread principale

Il modello della catena di continuazione funziona bene con le operazioni avviate dagli utenti mediante interazione con i controlli XAML. Gran parte delle operazioni ha inizio nel thread principale con l'invocazione a una proprietà del modello di visualizzazione associata a una proprietà del controllo XAML. Il modello di visualizzazione crea una catena di continuazione che esegue alcune attività nel thread principale e altre in background. L'aggiornamento dell'interfaccia utente con il risultato dell'operazione avviene nel thread principale.

Ma non tutti gli aggiornamenti all'interfaccia utente sono in risposta a operazioni avviate nel thread principale. Alcuni aggiornamenti possono provenire da origini esterne, come pacchetti o dispositivi di rete. In questi casi, è possibile che l'app debba aggiornare i controlli XAML nel thread principale al di fuori di un contesto di continuazione. Una catena di continuazione potrebbe anche non essere la soluzione adatta.

Questa restrizione può essere gestita in due modi. Uno è di creare un'attività che non fa niente e poi programmare una continuazione di quell'attività eseguita nel thread principale utilizzando il risultato della funzione concurrency::task_continuation_context::use_current come argomento del metodo task::then. Questo approccio semplifica la gestione delle eccezioni che si verificano durante l'operazione. Puoi gestire le eccezioni con una continuazione basata su attività. La seconda opzione è conosciuta ai programmatori Win32. Puoi utilizzare il metodo RunAsync della classe CoreDispatcher per eseguire una funzione nel thread principale. Puoi avere accesso a un oggetto CoreDispatcher dalla proprietà Dispatcher dell'oggetto CoreWindow. Se usi il metodo RunAsync, devi decidere come gestire le eccezioni. Per impostazione predefinita, invochi RunAsync e ne ignori il valore restituito, tutte le eventuali eccezioni che si verificano durante l'operazione andranno perse. Se non vuoi perdere le eccezioni, puoi eseguire il wrapping dell'handle IAsyncAction^ restituito dal metodo RunAsync con un'attività PPL e aggiungere una continuazione basata su attività che osserva eventuali eccezioni dell'attività.

Ecco un esempio tratto da 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();
    })); 
} 


Questa funzione di supporto esegue funzioni inviate al thread principale con una priorità bassa, in modo da non competere con azioni nell'interfaccia utente.

Uso dell'Asynchronous Agents Library

Gli agenti sono un modello utile per la programmazione parallela, specialmente nelle app orientate al flusso come le interfacce utente. Usa i buffer dei messaggi per interagire con gli agenti. Per altre info, vedi Asynchronous Agents Library.

[In alto]

Suggerimenti di programmazione asincrona in Windows Store con C++.

Ecco qualche suggerimento e qualche linea guida per aiutarti a scrivere codice asincrono efficace nelle app di Windows Store in C++.

Non effettuare la programmazione diretta con i thread

Sebbene le app di Windows Store non siano in grado di creare direttamente nuovi thread, puoi comunque contare appieno sulle librerie C++, come le librerie PPL e Asynchronous Agents Library. In generale, usa sempre le funzionalità di queste librerie per il codice nuovo, invece di effettuare la programmazione diretta con i thread.

Nota  Durante il porting del codice esistente puoi continuare a utilizzare il pool di thread. Per un esempio di codice, vedi Esempio di pool di thread.

Usa "Async" nel nome delle funzioni asincrone.

Quando scrivi una funzione o un metodo con funzionamento asincrono, usa "Async" nel per poterli distinguere immediatamente. Questa convenzione si applica a tutte le funzioni e i metodi asincroni di Hilo. Si applica inoltre a tutte le funzioni e i metodi asincroni di Windows Runtime.

Esegui il wrapping di tutte le operazioni asincrone di Windows Runtime con attività PPL.

In Hilo, eseguiamo il wrapping di tutte le operazioni asincrone provenienti da Windows Runtime in un'attività PPL mediante la funzione concurrency::create_task. Abbiamo constatato che le attività PPL sono più pratiche rispetto alle interfacce asincrone di livello inferiore di Windows Runtime. A meno di non usare la libreria PPL o di scrivere codice di wrapping personalizzato che controlla i valori HRESULT, l'app ignorerà tutti gli errori che si verificano durante le operazioni asincrone di Windows Runtime. Per contro, un 'operazione asincrona il cui wrapping è stato eseguito con un'attività, propagherà automaticamente gli errori di run-time mediante eccezioni differite PPL.

Inoltre, le attività PPL ti forniscono una sintassi di facile utilizzo per il modello della catena di continuazione.

L'unica eccezione a queste indicazioni si ha nel caso delle funzioni dette "Fire and Forget" che, insolitamente, non richiedono la notifica delle eccezioni.

Restituisci le attività PPL dalle funzioni asincrone interne dell'app

Ti consigliamo di restituire le attività PPL di tutte le funzioni e i metodi asincroni dell'app, a meno che non si tratti di metodi pubblici, protetti o pubblici protetti di classi ref pubbliche. Interfacce derivate da IAsyncInfo solo per metodi asincroni pubblici delle classi ref pubbliche.

Restituisci le interfacce derivate da IAsyncInfo dai metodi asincroni pubblici delle classi ref pubbliche.

Il compilatore richiede che tutti i metodi asincroni pubblici, protetti o pubblici protetti delle classi ref pubbliche dell'app restituiscano il tipo IAsyncInfo. L'interfaccia vera e propria deve essere un handle di una delle quattro interfacce derivate dall'interfaccia IAsyncInfo:

In genere, le app espongono i metodi asincroni pubblici nelle classi ref pubbliche solo in caso di interoperabilità con l'interfaccia ABI (Application Binary Interface).

Usa le classi ref pubbliche solo per l'interoperabilità

Usa le classi ref pubbliche per garantire l'interoperabilità nell'interfaccia ABI. Ad esempio, per le classi del modello di visualizzazione che espongono le proprietà dell'associazione dati XAML devi usare le classi ref pubbliche.

Non dichiarare classi interne alla tua app utilizzando la parola chiave ref. Vi sono casi che sfuggono a questa indicazione, ad esempio quando crei un'implementazione privata di un interfaccia che deve essere passata attraverso l'interfaccia ABI. In questo caso devi usare una classe ref privata, ma si tratta di casi rari.

Analogamente, ti consigliamo di utilizzare i tipi di dati dello spazio dei nomi Platform, come Platform::String, principalmente nelle classi ref pubbliche e per comunicare con le funzioni di Windows Runtime. Puoi usare std::wstring e wchar_t * all'interno della tua app ed effettuare la conversione a un handle Platform::String^ quando attraversi l'interfaccia ABI.

Usa codice C++ standard moderno, incluso lo spazio dei nomi std

Per le app di Windows Store dovresti usare gli standard, le librerie e le tecniche di scrittura in codice C++ più recenti. Usa uno stile di programmazione moderno e includi le operazioni sui tipi di dati dello spazio dei nomi std. Ad esempio, Hilo usa i tipi std::shared_ptr e std::vector.

Solo per il livello più esterno dell'app devi usare il linguaggio C++/CX. Questo livello interagisce con il linguaggio XAML attraverso l'interfaccia ABI e probabilmente anche con altri linguaggio come JavaScript o C#.

Usa la cancellazione delle attività in modo uniforme

Esistono vari modi di implementare l'annullamento con le attività PPL e gli oggetti IAsyncInfo. Ti consigliamo di sceglierne uno e di utilizzare sempre quello per tutta l'app. Un approccio costante all'annullamento contribuisce a evitare errori di scrittura del codice e semplifica le revisioni del codice. Puoi decidere in base a quello che preferisci, questo in ogni caso è l'approccio che se è rivelato più indicato per Hilo.

  • Determiniamo il supporto dell'annullamento esterno basato su token per tutte le catene di continuazione e non per le singole attività di una catena di continuazione. In altre parole, se la prima attività di una continuazione supporta l'annullamento esterno mediante un token di annullamento, allora tutte le attività di quella catena, incluse quelle nidificate, supporteranno l'annullamento basato su token. Se la prima attività non supporta l'annullamento basato su token, allora nessun'altra attività della catena supporterà l'annullamento basato su token.
  • Creiamo un oggetto concurrency::cancellation_token_source tutte le volte che avviamo la creazione di una nuova catena di continuazione che deve fornire il supporto per l'annullamento che viene avviato all'esterno della stessa catena di continuazione. Ad esempio, in Hilo usiamo oggetti origine del token di annullamento per consentire l'annullamento delle operazioni asincrone in sospeso quando l'utente lascia la pagina corrente.
  • Chiamiamo il metodo get_token dell'origine del token di annullamento. Passiamo come argomento il token di annullamento alla funzione create_task che crea la prima attività della catena di continuazione. In caso di continuazioni nidificate, passiamo il token di annullamento alla prima attività di ogni singola catena di continuazione nidificata. Non passiamo un token di annullamento alle invocazioni task::then che creano continuazioni. Le continuazioni basate sul valore ereditano il token di annullamento dell'attività antecedente per impostazione predefinita e non hanno bisogno che venga fornito un argomento del token di annullamento. Le continuazioni basate su attività non ereditano i token di annullamento, ma in genere eseguono azioni di pulizia che sono necessarie sia che ci sia stato annullamento, sia che non ci sia stato.
  • Quando è possibile annullare le attività di lunga durata, chiamiamo periodicamente la funzione concurrency::is_task_cancellation_requested per verificare se ci sono richieste di annullamento in sospeso. Quindi, dopo avere eseguito le azioni di pulizia, chiamiamo la funzione concurrency::cancel_current_task per uscire dall'attività e propagare l'annullamento lungo la catena di continuazione.
  • Se un'attività antecedente utilizza altre tecniche di segnalazione per indicare l'annullamento, ad esempio restituire nullptr, chiamiamo la funzione cancel_current_task della continuazione per propagare l'annullamento lungo la catena di continuazione. Talvolta ricevere un puntatore null dall'attività antecedente è l'unico meccanismo possibile per annullare una catena di continuazione. Vale a dire che non tutte le catene di continuazione possono essere annullate dall'esterno. Ad esempio, in Hilo, talvolta annulliamo una catena di continuazione se l'utente sceglie il pulsante di annullamento della selezione file. In questo caso, l'operazione asincrona di selezione file restituisce nullptr.
  • Nella continuazione basata su attività usiamo un blocco try/catch tutte le volte che dobbiamo determinare se l'attività precedente in una catena di continuazione è stata annullata. In questo caso, rileviamo l'eccezione concurrency::task_canceled.
  • Nell'oggetto funzione ObserveException di Hilo c'è un blocco try/catch che impedisce all'eccezione task_canceled di passare inosservata. Il blocco interrompe la terminazione dell'app quando si verifica un annullamento e non esiste altro modo specifico di gestire l'eccezione.
  • Se un componente di Hilo crea un'attività annullabile per conto di un'altra parte dell'app, ci assicuriamo che la funzione che crea l'attività accetti cancellation_token come argomento. In questo modo il chiamante può specificare il comportamento di annullamento.

Gestisci le eccezioni delle attività con una continuazione basata su attività

È utile aggiungere una continuazione al termine di ogni catena di continuazione che rileva le eccezioni con lambda finale. Consigliamo di rilevare solo le eccezioni che sai come gestire. Le altre eccezioni verrano rilevate dal run-time e causeranno la terminazione dell'app con la funzione std::terminate di errore immediato.

In Hilo, tutte le catene di continuazione terminano con una continuazione che chiama l'oggetto funzione ObserveException di Hilo. Per altre info e una simulazione di codice, vedi Uso delle continuazioni basate su attività per la gestione delle eccezioni in questa pagina.

Gestisci le eccezioni localmente quando usi la funzione when_all

La funzione when_all viene restituita immediatamente quando una qualsiasi di queste attività genera un'eccezione. In questo caso, è possibile che alcune attività siano ancora in esecuzione. Quando si verifica un'eccezione, il gestore deve attendere le attività in sospeso.

Per evitare le complicazioni dovute alla necessità di verificare la presenza di eccezioni e di attendere il completamento delle attività, gestisci le eccezioni localmente all'interno delle attività gestite da when_all.

Ad esempio, per caricare le immagini di anteprima, Hilo usa when_all Le eccezioni sono gestite nelle attività, e non ripropagate in when_all. Hilo passa alle attività un oggetto criteri di eccezione configurabile per assicurarsi che le attività possano usare il criterio di eccezione globale dell'app. Ecco il codice.

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


Ecco il codice per ciascuna attività.

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


Chiama gli oggetti modello di visualizzazione solo dal thread principale

Alcuni metodi e proprietà degli oggetti modelli di visualizzazione sono associati ai controlli XAML tramite associazione dati. Quando l'interfaccia utente invoca questi metodi e proprietà, utilizza il thread principale. Inoltre, per convenzione, chiama tutti i metodi e le proprietà degli oggetti modello di visualizzazione nel thread principale.

Usa i thread in background tutte le volte che è possibile

Se la prima attività di una catena di continuazione esegue il wrapping di un'operazione asincrona di Windows Runtime, per impostazione predefinita le continuazioni verranno eseguite nello stesso thread del metodo task::then, che generalmente è il thread principale. Questa impostazione predefinita è adeguata per le operazioni che interagiscono con i controlli XAML, ma non per molti tipi di elaborazione specifica dell'app, come l'applicazione di filtri di immagine o l'esecuzione di altre operazioni complesse.

Quando crei le continuazioni che devono essere eseguite in background nel pool di thread, assicurati di usare come argomento del metodo task::then il valore restituito dalla funzione task_continuation_context::use_arbitrary .

Non chiamare le operazioni di blocco dal thread principale

Non chiamare le operazioni di lunga durata dal thread principale. Come regola generale, è meglio non chiamare funzioni scritte dall'utente che bloccano il thread principale per più di 50 millisecondi per volta. Se devi chiamare una funzione di lunga durata, esegui il wrapping della funzione in un'attività eseguita in background.

Nota  Questo suggerimento riguarda solo la durata del blocco del thread principale e non il tempo necessario per completare un'operazione asincrona. Se l'operazione asincrona dura a lungo, fornisci all'utente un'indicazione visiva del suo stato e mantieni sbloccato il thread principale.

Per altre info, vedi l'argomento su come mantenere la velocità di risposta del thread dell'interfaccia utente (app di Windows Store in C#/VB/C++ e XAML).

Non chiamare task::wait dal thread principale

Non chiamare il metodo task::wait dal thread principale, né da qualsiasi thread STA. Se devi eseguire qualche operazione nel thread principale dopo il completamento dell'attività PPL, usa il metodo task::then per creare un'attività di continuazione.

Ricordati che esistono speciali regole di contesto per le continuazioni delle attività che eseguono il wrapping degli oggetti asincroni

Le app di Windows Store in C++ usano l'opzione /ZW del compilatore, che influisce sul comportamento di diverse funzioni. Se usi queste funzioni per applicazioni desktop o console, tieni presenti le differenze dovute all'utilizzo o meno dell'opzione.

Con l'opzione /ZW, quando invochi il metodo task::then su un'attività che esegue il wrapping di un oggetto IAsyncInfo, il contesto di runtime predefinito della continuazione è il contesto corrente. Il contesto corrente è il thread che ha invocato il metodo then. Questa impostazione predefinita si applica a tutte le continuazioni di una catena di continuazione, e non solo alla prima. Questa impostazione predefinita è una peculiarità delle app per Windows Store che semplifica la scrittura di codice. Nella maggior parte dei casi, le continuazioni delle operazioni di Windows Runtime devono interagire con oggetti che richiedono l'esecuzione dal thread principale dell'app Per un esempio, vedi Esempi di utilizzo delle catene di continuazione in questa pagina.

Quando un'attività non esegue il wrapping di una delle interfacce IAsyncInfo, il metodo then produce una continuazione che, per impostazione predefinita, viene eseguita su un thread scelto dal sistema nel pool di thread.

Per ignorare il comportamento predefinito, puoi passare al metodo task::then un ulteriore parametro del contesto di continuazione. In Hilo, indichiamo sempre questo parametro facoltativo se la prima attività della catena di continuazione è stata creata da un componente separato o da un metodo di supporto del componente corrente.

Nota  In Hilo, ci è sembrato utile chiarire la nostra concezione del contesto del thread delle subroutine mediante le istruzioni assert(IsMainThread()) e assert(IsBackgroundThread()). Nella versione di debug di Hilo, queste istruzioni generano un'eccezione se il thread utilizzato è diverso da quello dichiarato nell'asserzione. Le implementazioni delle funzioni IsMainThread e IsBackgroundThread sono nel codice sorgente di Hilo.

Ricordati che esistono speciali regole di contesto per la funzione create_async

Con l'opzione /ZW, il file di intestazione ppltasks.h include la funzione concurrency::create_async grazie alla quale puoi creare attività il cui wrapping viene eseguito in automatico da une delle interfacce IAsyncInfo di Windows Runtime. Le funzioni create_task e create_async hanno una sintassi e un comportamento simili, ma presentano anche qualche differenza.

Quando passi come argomento una funzione di lavoro alla funzione create_async, questa funzione lavoro può restituire a una qualsiasi delle interfacce derivate da IAsyncInfo, come IAsyncAction^ e IAsyncOperation<T>^, un valore void, T di tipo ordinario, task<T> o un handle.

Quando chiami la funzione create_async, la funzione di lavoro viene eseguita in modo sincrono nel contesto corrente o asincrono in background, a seconda del tipo di funzione di lavoro restituita. Se la funzione di lavoro restituita è di tipo void o T ordinario, la funzione di lavoro verrà eseguita in background nel pool di thread. Se la funzione di lavoro restituita è un'attività o un handle di una delle interfacce derivanti da IAsyncInfo, allora verrà eseguita in modo sincrono nel thread corrente. L'esecuzione della funzione di lavoro in modo sincrono è utile nel caso in cui si tratti di una funzione di piccole dimensioni che esegue una piccola parte di installazione prima di invocare un'altra funzione asincrona.

Nota  A differenza di quanto avviene per la funzione create_async, quando chiami la funzione create_task questa viene sempre eseguita in background su un pool di thread, indipendentemente dal tipo di funzione di lavoro restituita

Nota  In Hilo, ci è sembrato utile chiarire la nostra concezione del contesto del thread delle subroutine mediante le istruzioni assert(IsMainThread()) e assert(IsBackgroundThread()). Nella versione di debug di Hilo, queste istruzioni generano un'eccezione se il thread utilizzato è diverso da quello dichiarato nell'asserzione. Le implementazioni delle funzioni IsMainThread e IsBackgroundThread sono nel codice sorgente di Hilo.

Ricordati dei requisiti del contenitore di app per la programmazione parallela

Nelle app di Windows Store puoi usare la libreria PPL e l'Asynchronous Agents Library, ma non l'Utilità di pianificazione di Concurrency Runtime o i componenti di Gestione risorse.

Usa l'acquisizione esplicita delle espressioni lambda

È consigliabile esplicitare le variabili acquisite nelle espressioni lambda, e per questo motivo ti consigliamo di evitare l'uso dell'opzione [=] o [&] nelle espressioni lambda.

Inoltre, tieni presente la durata dell'oggetto quando acquisisci variabili nelle espressioni lambda. Per evitare errori di memoria, non acquisire per riferimento alcuna variabile contenente oggetti allocati nello stack. Inoltre, per lo stesso motivo evita di acquisire variabili membro delle classi temporanee.

Non creare riferimenti circolari tra classi ref ed espressioni lambda

Quando crei un'espressione lambda e acquisisci per valore l'handle this, il conteggio dei riferimenti dell'handle this viene incrementato. Se successivamente associ l'espressione lambda appena creata a una variabile membro della classe a cui this fa riferimento, crei un riferimento circolare che potrebbe portare a una perdita di memoria.

Quando l'espressione lambda risultante creerebbe un riferimento circolare, usa i riferimenti deboli per acquisire il riferimento this. Per un esempio di codice, vedi l'argomento su riferimenti deboli e interruzione di cicli (C++/CX).

Non ricorrere alla sincronizzazione se non è necessaria

Lo stile di programmazione delle app di Windows Store può creare alcune forme di sincronizzazione, ad esempio i blocchi, utilizzate più raramente. Quando più catene di continuazione sono in esecuzione contemporaneamente, possono utilizzare il thread principale come punto di sincronizzazione. Ad esempio, l'accesso alle variabili membro degli oggetti modello di visualizzazione avviene sempre dal thread principale, anche nelle app fortemente asincrone, perciò non sei costretto a sincronizzarlo con i blocchi.

In Hilo, siamo stati attenti ad accedere alle variabili membro delle classi del modello di visualizzazione solo dal thread principale.

Non dettagliare troppo la concorrenza

Usa le operazioni concorrenti solo per le attività di lunga durata>. L'impostazione di una chiamata asincrona comporta sempre un certo sovraccarico.

Evita le interazioni tra annullamento e gestione delle eccezioni

La libreria PPL implementa alcune parti della sua funzionalità di annullamento delle attività mediante le eccezioni. Se rilevi eccezioni di cui non sei a conoscenza, potresti interferire con il meccanismo di annullamento della libreria PPL.

Nota  Se usi i token di annullamento solo per segnalare l'annullamento e non chiami la funzione cancel_current_task, per l'annullamento non verranno usate eccezioni.

Nota  Se una continuazione deve controllare gli annullamenti o le eccezioni che si sono verificate nelle attività in una fase precedente della catena di continuazione, assicurati che la continuazione sia basata su attività

Usa i modelli paralleli

Se la tua app esegue calcoli complessi, molto probabilmente devi ricorrere a tecniche di programmazione parallela. Esistono vari modelli consolidati per utilizzare efficacemente hardware multicore. Parallel Programming with Microsoft Visual C++ è una risorsa contenente alcuni dei modelli più usati, con esempi di utilizzo della libreria PPL e l'Asynchronous Agents Library.

Ricordati dei requisiti di test speciali per le operazioni asincrone

Gli unit test richiedono una speciale gestione della sincronizzazione quando ci sono chiamate asincrone. Gran parte dei framework di test non consente di attendere i risultati asincroni per il test. Perciò, devi ricorrere a codice particolare che ti consenta di aggirare il problema.

Inoltre, quando esegui il test di componenti che richiedono alcune operazioni per potersi verificare nel thread principale, devi trovare il modo di assicurarti che il framework di test venga eseguito nel contesto di thread richiesto.

In Hilo, abbiamo risolto entrambi i problemi ricorrendo a codice di test personalizzato. Per una descrizione di quello che abbiamo fatto, vedi l'argomento sulla verifica delle app in questa guida.

Usa macchine a stato finito per gestire le operazioni con interfoliazione

I programmi asincroni hanno più percorsi di esecuzione possibili e potenziali interazioni con le funzionalità dei programmi sincroni. Ad esempio, se la tua app consente agli utenti di premere il pulsante Indietro mentre è in esecuzione un'operazione asincrona, ad esempio il caricamento di un file, devi tenere conto di questo nella logica dell'app. Puoi annullare l'operazione in sospeso e ripristinare lo stato dell'app precedente all'avvio dell'operazione.

Consentire l'interfoliazione flessibile delle operazioni dell'app contribuisce a rafforzare nell'utente una percezione di velocità di esecuzione e di risposta dell'app. Si tratta di una caratteristica importante delle app di Windows Store, ma bisogna fare attenzione perché può anche originare bug.

Una gestione corretta dei vari percorsi di esecuzione e interazioni possibili con un'interfaccia utente asincrona richiede il ricorso a tecniche di programmazione particolari. Le macchine a stato finito rappresentano un'ottima scelta e facilitano l'implementazione di una gestione affidabile delle operazioni con interfoliazione relative alle operazioni asincrone. Le macchine a stato finito consentono di descrivere in modo esplicito cosa si verifica in ogni situazione possibile.

Ecco uno schema di una macchina a stato finito proveniente dal modello di visualizzazione del visualizzatore immagini di Hilo.

Macchina a stato finito del modello di visualizzazione del visualizzatore immagini

Tutte le cornici dello schema rappresentano una modalità operativa distinta adottabile da un'istanza della classe ImageBrowserViewModel in fase di esecuzione. Gli archi rappresentano transizioni, a cui è stato assegnato un nome, tra una modalità e l'altra. Alcune transizioni di stato si verificano quando un'operazione asincrona termina. Si riconoscono perché contengono la parola Finish nel nome. Tutte le altre si verificano all'avvio di un'operazione asincrona.

Lo schema mostra cosa può succedere nelle varie modalità operative. Ad esempio, se il visualizzatore immagini è in modalità Running e si verifica un'azione OnNavigatedFrom, la modalità risultante sarà Pending. Le transizioni sono il risultato di

  • azioni degli utenti, ad esempio richieste di navigazione
  • azioni di programmazione, ad esempio il completamento di un'operazione asincrona avviata in precedenza
  • eventi esterni, ad esempio notifiche di rete o eventi del file system

Non tutte le transizioni sono disponibili in ciascuna modalità. Ad esempio, la transizione con etichetta FinishMonthAndYearQueries può verificarsi solo in modalità Running.

Nel codice, le transizioni con nome sono funzioni membro della classe ImageBrowserViewModel. La modalità operativa corrente del visualizzatore immagini corrisponde al valore della variabile membro m_currentMode, che memorizza i valori dell'enumerazione ImageBrowserViewModel::Mode.

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


Ecco un esempio del codice che implementa la transizione ObserveFileChange nello schema.

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


Il metodo ObserveFileChange risponde alle notifiche del sistema operativo relative a modifiche esterne apportate ai file immagine che l'utente sta visualizzando. I casi da prendere in considerazione sono quattro, a seconda di quello che si sta verificando nell'app al momento della notifica. Ad esempio, se il visualizzatore immagini al momento sta eseguendo una query asincrona, sarà in modalità Running. In questa modalità, l'azione ObserveFileChange annulla la query in esecuzione perché i risultati non servono più, quindi avvia una nuova query. La modalità operativa resta Running.

[In alto]

 

 

Mostra:
© 2014 Microsoft