Padrões de programação assíncrona

Padrões e dicas de programação assíncrona no Hilo (aplicativos da Windows Store em C++ e XAML)

[ Este artigo destina-se aos desenvolvedores do Windows 8.x e do Windows Phone 8.x que escrevem aplicativos do Windows Runtime. Se você estiver desenvolvendo para o Windows 10, consulte documentação mais recente]

De: Desenvolvendo um aplicativo da Windows Store de ponta a ponta em C++ e XAML: Hilo

Logotipo padrões & práticas

Página anterior | Próxima página

O Hilo contém muitos exemplos de cadeias de continuação em C++, que são um padrão comum de programação assíncrona em aplicativos da Windows Store. Veja aqui algumas dicas e orientações para usar cadeias de continuação, além de exemplos das várias maneiras como você pode usá-las em seu aplicativo.

Baixar

Baixar o exemplo do Hilo
Baixar o manual (PDF)

Depois de baixar o código, veja as instruções em Aprenda a usar o Hilo.

Você aprenderá

  • Práticas recomendadas de programação assíncrona em aplicativos da Windows Store em C++
  • Como cancelar operações assíncronas pendentes.
  • Como manipular exceções que ocorrem em operações assíncronas.
  • Como usar tarefas paralelas com aplicativos da Windows Store escritos em Visual C++.

Aplica-se a

  • Tempo de Execução do Windows para o Windows 8
  • Extensões de componentes do Visual C++ (C++/CX)
  • Biblioteca de Padrões Paralelos (PPL)

Formas de usar o padrão de cadeia de continuação

As técnicas de programação assíncrona que nós usamos no Hilo estão no padrão geral conhecido como cadeia de continuação ou .then ladder (pronunciado ponto-then ladder). Uma cadeia de continuação é uma sequência de tarefas PPL conectadas por relações de fluxo de dados. A saída de cada tarefa se torna a entrada da próxima continuação na cadeia. O início da cadeia geralmente é uma tarefa que encapsula uma operação assíncrona do Tempo de Execução do Windows (uma interface derivada de IAsyncInfo). Nós apresentamos as cadeias de continuação em Escrevendo código C++ moderno para aplicativos da Windows Store neste guia.

Veja aqui um exemplo do Hilo que mostra uma cadeia de continuação que começa com uma tarefa que encapsula uma interface derivada de 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());
}


Neste exemplo, o contexto de chamada esperado para o método FileSystemRepository::GetSinglePhotoAsync é o thread principal. O método cria uma tarefa PPL que encapsula o resultado do método GetFilesAsync. O método GetFilesAsync é uma função auxiliar que retorna um identificador IAsyncOperation< IVectorView<StorageFile^>^>^.

O método task::then cria continuações que são executadas no mesmo thread ou em um thread diferente da tarefa que as precede, dependendo de como você as configura. Nesse exemplo, há uma continuação.

Observação   Como a tarefa inicial criada pela função concurrency::create_task encapsula um tipo derivado de IAsyncInfo, suas continuações são executadas por padrão no contexto que cria a cadeia de continuação que, nesse exemplo, é o thread principal.
 

Algumas operações, como as que interagem com controles XAML, devem ocorrer no thread principal. Por exemplo, o objeto PhotoImage pode ser vinculado a um controle XAML e, portanto, sua instância deve ser criada no thread principal. Por outro lado, você deve chamar operações de bloqueio somente de um thread em segundo plano e não do thread principal de seu aplicativo. Então, para evitar erros de programação, você precisa saber se cada continuação usará o thread principal ou um thread em segundo plano do pool de threads.

Existem diversas maneiras de usar o padrão da cadeia de continuação, dependendo da situação. Veja aqui algumas variações do padrão básico da cadeia de continuação:

Continuações baseadas em valores e em tarefas

A tarefa que precede uma continuação é chamada de tarefa antecessora da continuação ou antecedente. Se uma tarefa antecessora é do tipo task<T>, uma continuação dessa tarefa pode aceitar o tipo T ou o tipo task<T> como seu tipo de argumento. As continuações que aceitam o tipo T são continuações baseadas em valor. As continuações que aceitam o tipo task<T> são continuações baseadas em tarefa.

O argumento de uma continuação baseada em valor é o valor de retorno da função de trabalho da tarefa antecessora da continuação. O argumento de uma continuação baseada em tarefa é a própria tarefa antecessora. Você pode usar o método task::get para consultar a saída da tarefa antecessora.

Há várias diferenças de comportamento entre as continuações baseadas em valor e baseadas em tarefa. As continuações baseadas em valor não são executadas se a tarefa antecessora foi encerrada em uma exceção. Em contraste, as continuações baseadas em tarefa são executadas independentemente do status de exceção da tarefa antecessora. Além disso, as continuações baseadas em valor herdam o token de cancelamento de seu antecedente por padrão, enquanto as continuações baseadas em tarefa não herdam.

A maioria das continuações são baseadas em valor. Use continuações baseadas em tarefa em cenários que envolvem manipulação de exceção e cancelamento. Veja exemplos em Permitindo que cadeias de continuação sejam canceladas externamente e Usando continuações baseadas em tarefa para a manipulação de exceção posteriormente neste tópico.

Tarefas descodificadas

Na maioria dos casos, a função de trabalho de uma tarefa PPL retorna o tipo T se a tarefa é do tipo task<T>, mas o retorno do tipo T não é a única possibilidade. Você também pode retornar um valor de task<T> da função de trabalho que passa para as funções create_task ou task::then. Nesse caso, você pode esperar que PPL crie uma tarefa com o tipo task<task<T>>, mas isso não é o que acontece. Em vez disso, a tarefa resultante tem o tipo task<T>. A transformação automática de task<task<T>> em task<T> é chamada de descodificar uma tarefa.

A descodificação ocorre em nível de tipo. A PPL agenda a tarefa mais interna e usa seu resultado como resultado da tarefa externa. A tarefa mais externa é concluída quando a tarefa interna termina seu trabalho.

Tarefas descodificadas

A tarefa 1 no diagrama tem uma função de trabalho que retorna int. O tipo da tarefa 1 é task<int>. A tarefa 2 tem uma função de trabalho que retorna task<int>, que é a tarefa 2a no diagrama. A tarefa 2 aguarda a conclusão da tarefa 2a e retorna o resultado da função de trabalho da tarefa 2a como resultado da tarefa 2.

A descodificação das tarefas ajuda a colocar uma lógica de fluxo de dados condicional em redes de tarefas relacionadas. Veja em Usando continuações aninhadas para lógica condicional posteriormente nesta página os cenários e exemplos que exigem a PPL para descodificar tarefas. Para obter uma tabela de tipos de retorno, veja "Tipos de retorno da função lambda e tipos de retorno de tarefas" em Programação assíncrona em C++.

Permitindo que cadeias de continuação sejam canceladas externamente

Você pode criar cadeias de continuação que respondem a solicitações de cancelamento externas. Por exemplo, quando você exibe a página do navegador de imagens no Hilo, a página inicia uma operação assíncrona que cria grupos de fotos por mês e os exibe. Se você navega para fora da página antes que a operação de exibição termine, a operação pendente que criar os controles de grupos mensais é cancelada. Veja aqui as etapas.

  1. Crie um objeto concurrency::cancellation_token_source.
  2. Consulte um concurrency::cancellation_token na fonte do token de cancelamento usando o método cancellation_token_source::get_token.
  3. Passe o token de cancelamento como um argumento para a função concurrency::create_task da tarefa inicial na cadeia de continuação ou para o método task::then de qualquer continuação. Você só precisa fazer isso uma vez para cada cadeia de continuação, pois as tarefas de continuação subsequentes usam o contexto de cancelamento de sua tarefa antecedente por padrão.
  4. Para cancelar a cadeia de continuação enquanto ela está em execução, chame o método cancel da fonte do token de cancelamento do contexto de qualquer thread.

Veja aqui um exemplo.

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

O método StartMonthAndYearQueries cria os objetos concurrency::cancellation_token_source e concurrency::cancellation_token e passa o token de cancelamento para os métodos StartMonthQuery e StartYearQuery. Veja a seguir a implementação do método 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));
    });
}


O método StartMonthQuery cria uma cadeia de continuação. O cabeçalho da cadeia de continuação e a primeira tarefa de continuação da cadeia são construídos pelo método GetMonthGroupedPhotosWithCacheAsync da 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);
}

O código passa um token de cancelamento para o método task::then na primeira tarefa de continuação.

As consultas de ano e mês levarão alguns segundos para serem executadas se houver muitas imagens para processamento. É possível que o usuário saia da página enquanto elas estiverem em execução. Se isso acontecer, as consultas serão canceladas. O método OnNavigatedFrom do aplicativo Hilo chama o método CancelMonthAndYearQueries. O CancelMonthAndYearQueries da classe ImageBrowserViewModel invoca o método cancel da fonte de token de cancelamento.

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

O cancelamento na PPL é cooperativo. Você pode controlar o comportamento de cancelamento de tarefas que implementa. Para obter os detalhes do que acontece quando você chama o método cancel da fonte do token de cancelamento, veja "Cancelando tarefas" em Programação assíncrona em C++.

Outras maneiras de sinalizar o cancelamento

Uma continuação baseada em valor não será iniciada se o método cancel de sua fonte de token de cancelamento associada tiver sido chamado. Também é possível que o método cancel seja chamado enquanto uma tarefa em uma cadeia de continuação está em execução. Se você quer saber se uma solicitação de cancelamento externa foi sinalizada durante a execução de uma tarefa, pode chamar a função concurrency::is_task_cancellation_requested para descobrir. A função is_task_cancellation_requested verifica o status do token de cancelamento da tarefa atual. A função retorna true se o método cancel foi chamado no objeto da fonte do token de cancelamento que criou o token de cancelamento. (Veja um exemplo em Usando o C++ no exemplo do Bing Maps Trip Optimizer.)

Observação  Você pode chamar is_task_cancellation_requested do corpo da função de trabalho para qualquer tipo de tarefa, inclusive tarefas criadas pela função create_task, pela função create_async e pelo método task::then.
 
Dica  Não chame is_task_cancellation_requested em todas as iterações de um loop coeso. Em geral, se você quer detectar uma solicitação de cancelamento em uma tarefa em execução, sonde o cancelamento com uma frequência suficiente para obter o tempo de resposta desejado sem afetar o desempenho do aplicativo quando o cancelamento não é solicitado.
 

Depois de detectar que uma tarefa em execução deve processar uma solicitação de cancelamento, execute a limpeza necessária para o aplicativo e chame a função concurrency::cancel_current_task para finalizar a tarefa e notificar o tempo de execução de que o cancelamento ocorreu.

Os tokens de cancelamento não são a única forma de sinalizar solicitações de cancelamento. Quando você chama funções de biblioteca assíncronas, às vezes recebe um valor de sinal especial da tarefa antecessora, como nullptr, para indicar que foi solicitado um cancelamento.

Cancelando operações assíncronas encapsuladas por tarefas

As tarefas de PPL ajudam a dar suporte ao cancelamento em aplicativos que usam as operações assíncronas do Tempo de Execução do Windows. Quando você encapsula uma interface derivada de IAsyncInfo em uma tarefa, a PPL notifica a operação assíncrona (chamando o método IAsyncInfo::Cancel) quando chega uma solicitação de cancelamento.

Observação  concurrency::cancellation_token da PPL é um tipo C++ que não pode cruzar a ABI. Isso quer dizer que você não pode passá-lo como um argumento para um método público de uma classe ref pública. Se você precisa de um token de cancelamento nessa situação, encapsule a chamada para o método assíncrono público de sua classe ref pública em uma tarefa de PPL que use um token de cancelamento. Depois, chame a função concurrency::is_task_cancellation_requested da função de trabalho da função create_async dentro do método assíncrono. A função is_task_cancellation_requested tem acesso ao token da tarefa de PPL e retorna true se o token foi sinalizado.
 

Usando continuações baseadas em tarefas para a manipulação de exceção

As tarefas de PPL dão suporte à manipulação de exceção adiada. Você pode usar uma continuação baseada em tarefa para capturar exceções que ocorreram em qualquer das etapas de uma cadeia de continuação.

No Hilo, nós verificamos as exceções sistematicamente, no final de cada cadeia de continuação. Para facilitar isso, criamos uma classe auxiliar que procura exceções e as manipula de acordo com uma política configurável. Este é o código.

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


Montando as saídas de várias continuações

As cadeias de continuação usam um estilo de programação de fluxo de dados onde o valor de retorno de uma tarefa antecessora se torna a entrada de uma tarefa de continuação. Em algumas situações, o valor de retorno da tarefa antecessora não contém tudo o que é necessário para a próxima etapa. Por exemplo, talvez você precise mesclar os resultados de duas tarefas de continuação para que uma terceira continuação possa continuar. Há várias técnicas para lidar com esse caso. No Hilo, nós usamos um ponteiro compartilhado (std::shared_ptr) para manter temporariamente valores intermediários no heap.

Por exemplo, o diagrama a seguir mostra o fluxo de dados e as relações de fluxo de controle do método ThumbnailGenerator::CreateThumbnailFromPictureFileAsync do Hilo, que usa uma cadeia de continuação para criar imagens de miniatura. A criação de uma miniatura a partir de um arquivo de imagem exige etapas assíncronas que não se ajudam a um padrão fluxo de dados em linha reta.

No diagrama, os ovais sólidos representam operações assíncronas no Tempo de Execução do Windows. Os ovais tracejados são tarefas que chamam funções assíncronas. Os arcos representam entradas e saídas. As setas tracejadas são dependências do fluxo de controle de operações com efeitos colaterais. Os números mostram a ordem em que as operações ocorrem na cadeia de continuação do método CreateThumbnailFromPictureFileAsync.

O método CreateThumbnailFromPictureFileAsync

O diagrama mostra interações que são muito complexas para o fluxo de dados linear. Por exemplo, a operação "Definir dados de pixel" síncrona exige entradas de três operações assíncronas separadas e modifica uma de suas entradas, o que causa uma restrição de sequenciamento de uma operação de liberação assíncrona subsequente. Veja aqui o código desse exemplo.

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


O exemplo chama a função std::make_shared para criar contêineres compartilhados de identificadores para o decodificador e objetos de provedor de pixel que serão criados pelas continuações. O operador * (desreferenciar) permite que as continuações definam os contêineres compartilhados e leiam seus valores.

Usando continuações aninhadas para lógica condicional

As continuações aninhadas permitem adiar a criação de partes de uma cadeia de continuação até o tempo de execução. As continuações aninhadas são úteis quando você tem tarefas condicionais na cadeia e precisa capturar variáveis locais de tarefas em uma cadeia de continuação para usar em tarefas de continuação subsequentes. As continuações aninhadas dependem da descodificação da tarefa.

A operação de rotação de imagem do Hilo usa continuações aninhadas para tratar a lógica condicional. A operação de rotação de comporta de forma diferente dependendo do formato de arquivo da imagem girada dar suporte a propriedade de orientação EXIF (Exchangeable Image File Format). Este é o código.

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


Mostrando o progresso de uma operação assíncrona

Embora o aplicativo Hilo não relate o progresso de operações em andamento usando uma barra de progresso, você pode fazê-lo. Para saber mais, veja a postagem do blog Mantendo os aplicativos rápidos e fluidos com assincronia no Tempo de Execução do Windows, que tem alguns exemplos de como usar as interfaces IAsyncActionWithProgress<TProgress> e IAsyncOperationWithProgress<TProgress, TResult>.

Observação  O Hilo mostra o controle de anel de progresso animado durante operações assíncronas de execução longa. Para saber mais sobre como o Hilo mostra o progresso, veja ProgressRing neste guia.
 

Criando tarefas em segundo plano com create_async para cenários de interoperabilidade

Às vezes, você precisa usar diretamente as interfaces derivadas de IAsyncInfo em classes que você define. Por exemplo, as assinaturas de métodos públicos de classes ref públicas em seu aplicativo não podem incluir tipos não ref, como task<T>. Em vez disso, você expõe operações assíncronas com uma das interfaces derivadas da interface IAsyncInfo. (Uma classe ref pública é uma classe do C++ que foi declarada com visibilidade pública e a palavra-chave ref do C++/CX.)

Você pode usar a função concurrency::create_async para expor uma tarefa de PPL como um objeto IAsyncInfo.

Veja Criando operações assíncronas em C++ para aplicativos da Windows Store para saber mais sobre a função create_async.

Expedindo funções para o thread principal

O padrão da cadeia de continuação funciona bem para operações que o usuário inicia interagindo com controles XAML. A maioria das operações começa no thread principal com uma invocação de uma propriedade do modelo de exibição associada a uma propriedade do controle XAML. O modelo de exibição cria uma cadeia de continuação que executa algumas tarefas no thread principal e algumas tarefas em segundo plano. A atualização da interface do usuário com o resultado da operação ocorre no thread principal.

Mas nem todas as atualizações da interface do usuário são respostas de operações iniciadas no thread principal. Algumas atualizações podem vir de fontes externas, como pacotes ou dispositivos de rede. Nessas situações, talvez seu aplicativo precise atualizar controles XAML no thread principal fora de um contexto de continuação. Uma cadeia de continuação pode não ser o que você precisa.

Há duas maneiras de lidar com essa restrição: Uma maneira é criar uma tarefa que não faz nada e agendar uma continuação dessa tarefa que é executada no thread principal usando o resultado da função concurrency::task_continuation_context::use_current como argumento para o método task::then. Essa abordagem facilita a manipulação de exceções que ocorrem durante a operação. Você pode manipular exceções com uma continuação baseada em tarefa. A segunda opção é conhecida dos programadores de Win32. Você pode usar o método RunAsync da classe CoreDispatcher para executar uma função no thread principal. Você pode obter acesso a um objeto CoreDispatcher da propriedade Dispatcher do objeto CoreWindow. Se você usa o método RunAsync, precisa decidir como manipular as exceções. Por padrão, se você invoca RunAsync e ignora seu valor de retorno, qualquer exceção que ocorre durante a operação é perdida. Se você não quer perder as exceções, pode encapsular identificador IAsyncAction^ retornado pelo método RunAsync com uma tarefa de PPL e adicionar uma continuação baseada em tarefa que observa todas as exceções da tarefa.

Veja aqui um exemplo do 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();
    })); 
} 


Essa função auxiliar executa funções que são expedidas para o thread principal com prioridade baixa, de forma que elas não concorram com as ações na interface do usuário.

Usando a biblioteca de agentes assíncronos

Os agentes são um modelo útil para a programação paralela, especialmente em aplicativos orientados por fluxo, como as interfaces do usuário. Use buffers de mensagens para interagir com agentes. Para saber mais, veja Biblioteca de agentes assíncronos.

[Início]

Dicas de programação assíncrona em aplicativos da Windows Store em C++

Veja a seguir algumas dicas e diretrizes que podem ajudá-lo a escrever código assíncrono eficiente em aplicativos da Windows Store em C++.

Não programe diretamente com threads.

Embora os aplicativos da Windows Store não possam criar novos threads diretamente, você ainda tem toda a potência das bibliotecas C++, como a PPL e a Biblioteca de agentes assíncronos. Em geral, use os recursos dessas bibliotecas para todo o novo código em vez de programar diretamente com threads.

Observação  Ao portar o código existente, você pode continuar usando threads do pool de threads. Veja em Exemplo do pool de threads um exemplo de código.
 

Use "Async" no nome de suas funções assíncronas

Quando você escreve uma função ou um método que opera de forma assíncrona, use "Async" como parte de seu nome para tornar isso claro imediatamente. Todas as funções e todos os métodos assíncronos no Hilo usam essa convenção. O Tempo de Execução do Windows também a usa para todas as funções e todos os métodos assíncronos.

Encapsule todas as operações assíncronas do Tempo de Execução do Windows com tarefas PPL

No Hilo, nós encapsulamos todas as operações assíncronas que obtemos do Tempo de Execução do Windows em uma tarefa de PPL usando a função concurrency::create_task. Nós descobrimos que as tarefas de PPL são mais convenientes para trabalhar que as interfaces assíncronas de nível inferior do Tempo de Execução do Windows. A menos que você use a PPL (ou escreva o código de encapsulamento personalizado que verifica valores de HRESULT), seu aplicativo vai ignorar todos os erros que ocorrerem durante as operações assíncronas do Tempo de Execução do Windows. Por outro lado, uma operação assíncrona encapsulada com uma tarefa propagará automaticamente os erros de tempo de execução usando exceções adiadas de PPL.

Além disso, as tarefas de PPL fornecem uma sintaxe fácil de usar para o padrão da cadeia de continuação.

A única exceção a essa orientação é ocado de funções do tipo disparar e esquecer, em que você não quer notificações de exceções, o que é uma situação incomum.

Retorne tarefas PPL de funções assíncronas internas em seu aplicativo

Recomendamos que você retorne tarefas de PPL de todas as funções e todos os métodos assíncronos em seu aplicativo, a menos que eles sejam públicos, protegidos ou métodos protegidos públicos de classes ref públicas. As interfaces derivadas de IAsyncInfo somente para métodos assíncronos públicos de classes ref públicas.

Retorne interfaces derivadas de IAsyncInfo de métodos assíncronos públicos de classes ref públicas

O compilador exige que qualquer método público, protegido ou assíncrono protegido público das classes ref públicas de seu aplicativo retornem um tipo IAsyncInfo. A interface real deve ser um identificador para uma das quatro interfaces derivadas da interface IAsyncInfo:

Em geral, os aplicativos exporão métodos assíncronos públicos em classes ref públicas somente nos casos de interoperabilidade de ABI (interface binária do aplicativo).

Use classes ref públicas somente para interoperabilidade

Use classes ref públicas para interoperabilidade pela ABI. Por exemplo, você deve usar classes ref públicas para classes do modelo de exibição que expõem propriedades de vinculação de dados XAML.

Não declare classes internas do aplicativo usando a palavra-chave ref. Em alguns casos, essa orientação não se aplica, por exemplo, quando você está criando uma implementação particular de uma interface que deve ser passada pela ABI. Nessa situação, você precisa de uma classe ref particular. Esses casos são raros.

De forma semelhante, recomendamos que você use os tipos de dados no namespace Platform como Platform::String principalmente em classes ref públicas e se comunique com funções de Tempo de Execução do Windows. Você pode usar std::wstring e wchar_t * no aplicativo e converter para um identificador Platform::String^ quando ultrapassar a ABI.

Use o C++ padrão moderno, incluindo o namespace std

Os aplicativos da Windows Store devem usar as técnicas de codificação, os padrões e as bibliotecas do C++ mais recentes. Use um estilo de programação moderno e inclua as operações de tipos de dados do namespace std. Por exemplo, o Hilo usa os tipos std::shared_ptr e std::vector.

Somente a camada mais externa de seu aplicativo precisa usar C++/CX. Essa camada interage com o XAML pela ABI e talvez com outras linguagens, como JavaScript ou C#.

Use o cancelamento de tarefas de forma consistente

Há várias maneiras de implementar o cancelamento usando tarefas de PPL e objetos IAsyncInfo. É uma boa ideia escolher uma forma e usá-la de maneira consistente em todo o aplicativo. Uma abordagem de cancelamento consistente ajuda a evitar erros de codificação e facilita as revisões do código. Sua decisão depende de sua vontade, mas o que funcionou bem para o Hilo foi o seguinte.

  • Nós determinamos o suporte para o cancelamento baseado em token externo para cada cadeia de continuação e não para cada tarefa em uma cadeia de continuação. Em outras palavras, se a primeira tarefa em uma continuação dá suporte ao cancelamento externo usando um token de cancelamento, todas as tarefas nessa cadeia, inclusive as tarefas aninhadas, dão suporte ao cancelamento baseado em token. Se a primeira tarefa não dá suporte ao cancelamento baseado em token, nenhuma tarefa da cadeia dá suporte ao cancelamento baseado em token.
  • Nós criamos um objeto concurrency::cancellation_token_source sempre que iniciamos a criação de uma nova cadeia de continuação que precisa dar suporte ao cancelamento que é iniciado fora da própria cadeia de continuação. Por exemplo, no Hilo nós usamos objetos de fonte do token de cancelamento para permitir que operações assíncronas pendentes sejam canceladas quando o usuário navega para fora da página atual.
  • Nós chamamos o método get_token da fonte do token de cancelamento. Nós passamos o token de cancelamento como um argumento para a função create_task que cria a primeira tarefa na cadeia de continuação. Se há continuações aninhadas, nós passamos o token de cancelamento para a primeira tarefa de cada cadeia de continuação aninhada. Nós não passamos um token de cancelamento para as invocações de task::then que criam continuações. As continuações baseadas em valor herdam o token de cancelamento de sua tarefa antecessora por padrão e não precisam que seja fornecido um argumento do token de cancelamento. As continuações baseadas em tarefa não herdam tokens de cancelamento, mas em geral executam ações de limpeza que são exigidas independente de o cancelamento ter ou não ocorrido.
  • Quando é possível cancelar tarefas de execução longa, chamamos a função concurrency::is_task_cancellation_requested periodicamente para verificar se há uma solicitação de cancelamento pendente. Então, depois de executar as ações de limpeza, chamamos a função concurrency::cancel_current_task para sair da tarefa e propagar o cancelamento ao longo da cadeia de continuação.
  • Quando uma tarefa antecessora usa técnicas de sinalização alternativas para indicar o cancelamento, como retornar nullptr, nós chamamos a função cancel_current_task na continuação para propagar o cancelamento juntamente com a cadeia de continuação. Às vezes, o recebimento de um ponteiro nulo da tarefa antecessora é o único mecanismo para cancelar uma cadeia de continuação. Em outras palavras, nem todas as cadeias de continuação podem ser canceladas externamente. Por exemplo, no Hilo, às vezes cancelamos uma cadeia de continuação quando o usuário seleciona o botão Cancelar do seletor de arquivos. Quando isso acontece, a operação de seleção de arquivos assíncrona retorna nullptr.
  • Nós usamos um bloco try/catch em uma continuação baseada em tarefa sempre que precisamos detectar se uma tarefa anterior em uma cadeia de continuação foi cancelada. Nessa situação, nós capturamos a exceção concurrency::task_canceled.
  • Há um bloco try/catch no objeto de função ObserveException do Hilo que impede que a exceção task_canceled não seja observada. Isso impede que o aplicativo seja encerrado quando ocorre um cancelamento e não existe nenhuma outra manipulação específica da exceção.
  • Se um componente do Hilo cria uma tarefa que pode ser cancelada em nome de outra parte do aplicativo, nós verificamos se a função de criação da tarefa aceita um cancellation_token como argumento. Assim, o chamador pode especificar o comportamento de cancelamento.

Manipule as exceções de tarefas usando uma continuação baseada em tarefa

É uma boa ideia ter uma continuação no final de cada cadeia de continuação que captura exceções com um lambda final. Recomendamos capturar apenas as exceções que você sabe como manipular. Outras exceções serão capturadas pelo tempo de execução e farão o aplicativo ser encerrado usando a função std::terminate de "falha rápida".

No Hilo, cada cadeia de continuação termina com uma continuação que chama o objeto da função ObserveException do Hilo. Veja Usando continuações baseadas em tarefa para a manipulação de exceção nesta página para saber mais e obter um passo a passo do código.

Manipule as exceções localmente ao usar a função when_all

A função when_all é retornada imediatamente quando qualquer de suas tarefas gera uma exceção. Se isso ocorrer, algumas das tarefas ainda podem estar em execução. Quando ocorre uma exceção, seu manipulador deve esperar as tarefas pendentes.

Para evitar a complexidade de testar exceções e esperar a conclusão das tarefas, manipule as exceções localmente nas tarefas que são gerenciadas por when_all.

Por exemplo, o Hilo usa when_all para carregar imagens de miniatura. As exceções são manipuladas nas tarefas e não propagadas de volta para when_all. O Hilo passa um objeto de política de exceção configurável para as tarefas a fim de garantir que as tarefas possam usar a política global de exceções do aplicativo. Este é o código.

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


Veja o código de cada tarefa.

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


Chame objetos do modelo de exibição somente do thread principal

Alguns métodos e propriedades de objetos do modelo de exibição são associados a controles XAML usando a vinculação de dados. Quando a interface do usuário invoca esses métodos e propriedades, ela usa o thread principal. Além disso, por convenção, chame todos os métodos e todas as propriedades de objetos do modelo de exibição no thread principal.

Use threads em segundo plano sempre que possível

Se a primeira tarefa em uma cadeia de continuação encapsula uma operação assíncrona do Tempo de Execução do Windows, por padrão, as continuações são executadas no mesmo thread que o método task::then. Normalmente, esse é o thread principal. Esse padrão é apropriado para operações que interagem com controles XAML, mas não é a opção certa para muitos tipos de processamento específico do aplicativo, como a aplicação de filtros de imagem ou a execução de outras operações que utilizam muitos recursos de computação.

Use o valor que a função task_continuation_context::use_arbitrary retorna como argumento para o método task::then ao criar continuações que devem ser executadas em segundo plano, em threads do pool de threads.

Não chame operações de bloqueio do thread principal

Não chame operações de execução longa no thread principal. Em geral, uma boa regra prática é não chamar funções escritas pelo usuário que bloqueiam o thread principal por mais de 50 milissegundos por vez. Se você precisa chamar uma função de execução longa, encapsule-a em uma tarefa que é executada em segundo plano.

Observação  Essa dica se refere somente ao tempo que você bloqueia o thread principal, não ao tempo necessário para concluir uma operação assíncrona. Se uma operação assíncrona é muito longa, dê ao usuário uma indicação visual de seu progresso, mantendo o thread principal desbloqueado.
 

Para saber mais, veja Mantenha a capacidade de resposta do thread da interface do usuário (aplicativos da Windows Store em C#/VB/C++ e XAML).

Não chame task::wait do thread principal

Não chame o método task::wait do thread principal (ou qualquer thread STA). Se você precisa fazer algo no thread principal depois que uma tarefa de PPL é concluída, use o método task::then para criar uma tarefa de continuação.

Esteja ciente das regras de contexto especiais para continuações de tarefas que encapsulam objetos assíncronos

Os aplicativos da Windows Store em C++ usam o comutador do compilador /ZW que afeta o comportamento de várias funções. Se você usa essas funções para aplicativos da área de trabalho ou de console, esteja ciente das diferenças causadas pelo uso do comutador.

Com o comutador /ZW, quando você invoca o método task::then em uma tarefa que encapsula um objeto IAsyncInfo, o contexto padrão do tempo de execução da continuação é o contexto atual. O contexto atual é o thread que invocou o método then. Esse padrão se aplica a todas as continuações em uma cadeia de continuação, não apenas à primeira. Esse padrão especial de aplicativos da Windows Store torna a codificação mais fácil. Na maioria dos casos, as continuações de operações de Tempo de Execução do Windows precisam interagir com objetos que exigem que você execute do thread principal do aplicativo. Para ver um exemplo, consulte Formas de usar o padrão de cadeia de continuação nesta página.

Quando uma tarefa não encapsula uma das interfaces IAsyncInfo, o método then produz uma continuação que é executada por padrão em um thread que o sistema escolhe no pool de threads.

Você pode substituir o comportamento padrão passando um parâmetro de contexto de continuação adicional para o método task::then. No Hilo, nós sempre fornecemos esse parâmetro adicional quando a primeira tarefa da cadeia de continuação foi criada por um componente separado ou um método auxiliar do componente atual.

Observação  No Hilo, nós achamos útil esclarecer nosso entendimento do contexto do thread para nossas sub-rotinas usando instruções assert(IsMainThread()) e assert(IsBackgroundThread()). Na versão de depuração do Hilo, essas instruções geram uma exceção se o thread usado é diferente do declarado na asserção. As implementações das funções IsMainThread e IsBackgroundThread estão na fonte do Hilo.
 

Esteja ciente das regras de contexto especiais para a função create_async

Com o comutador /ZW, o arquivo de cabeçalho ppltasks.h fornece a função concurrency::create_async, que permite que você crie tarefas que são encapsuladas automaticamente por uma das interfaces IAsyncInfo do Tempo de Execução do Windows. As funções create_task e create_async têm sintaxe e comportamento semelhantes, mas há algumas diferenças entre elas.

Quando você passa uma função de trabalho como argumento para a função create_async, essa função de trabalho pode retornar void, um tipo T comum, task<T> ou um identificador para qualquer das interfaces derivadas de IAsyncInfo, como IAsyncAction^ e IAsyncOperation<T>^.

Quando você chama a função create_async, a função de trabalho é executada de forma síncrona no contexto atual ou de forma assíncrona no segundo plano, dependendo do tipo de retorno da função de trabalho. Se o tipo de retorno da função de trabalho é nulo ou um tipo T comum, a função de trabalho é executada em segundo plano em um thread do pool de threads. Se o tipo de retorno da função de trabalho é uma tarefa ou um identificador para uma das interfaces derivadas de IAsyncInfo, a função de trabalho é executada de forma síncrona no thread atual. A execução da função de trabalho de forma síncrona é útil quando se trata de uma função pequena que faz uma configuração mínima antes de invocar outra função assíncrona.

Observação  Diferente da função create_async, quando você chama a função create_task, ela sempre é executada em segundo plano em um thread do pool de threads, independentemente do tipo de retorno da função de trabalho.
 
Observação  No Hilo, nós achamos útil esclarecer nosso entendimento do contexto do thread para nossas sub-rotinas usando instruções assert(IsMainThread()) e assert(IsBackgroundThread()). Na versão de depuração do Hilo, essas instruções geram uma exceção se o thread usado é diferente do declarado na asserção. As implementações das funções IsMainThread e IsBackgroundThread estão na fonte do Hilo.
 

Esteja ciente dos requisitos do contêiner de aplicativo para a programação paralela

Você pode usar a PPL e a Biblioteca de agentes assíncronos em um aplicativo da Windows Store, mas não pode usar os componentes do Gerenciador de Recursos ou o Agendador de Tarefas do Tempo de Execução de Simultaneidade.

Use a captura explícita para expressões lambda

É uma boa ideia ser explícito sobre as variáveis que você captura em expressões lambda. Por isso, não recomendamos o uso das opções [=] ou [&] para expressões lambda.

Além disso, esteja ciente do tempo de vida do objeto ao capturar variáveis em expressões lambda. Para evitar erros de memória, não capture por referência nenhuma variável que contenha objetos alocados pela pilha. Além disso, não capture variáveis membro de classes transitórias, pelo mesmo motivo.

Não crie referências circulares entre classes ref e expressões lambda

Quando você cria uma expressão lambda e captura o identificador this por valor, a contagem de referência do identificador this é incrementada. Se depois você associa a expressão lambda recém-criada a uma variável membro da classe referenciada por this, cria uma referência circular que pode gerar um vazamento de memória.

Use referências fracas para capturara a referência this quando o lambda resultante criaria uma referência circular. Para ver um exemplo de código, consulte Referências fracas e interrupção de ciclos (C++/CX).

Não use a sincronização desnecessariamente

O estilo de programação de aplicativos da Windows Store pode fazer algumas formas de sincronização, como bloqueios, serem necessários com menos frequência. Quando várias cadeias de continuação estão em execução ao mesmo tempo, elas podem facilmente usar o thread principal como ponto de sincronização. Por exemplo, as variáveis membro de objetos do modelo de exibição sempre são acessadas do thread principal, mesmo em um aplicativo altamente assíncrono, e, portanto você não precisa sincronizá-los com bloqueios.

No Hilo, nós fomos cuidadosos para acessar variáveis membro de classes do modelo de exibição somente do thread principal.

Não torne a simultaneidade muito refinada

Use operações simultâneas somente para tarefas de execução longa. Existe uma determinada sobrecarga na configuração de chamadas assíncronas.

Fique atento a interações entre manipulação de exceções e cancelamento

A PPL implementa algumas partes de sua funcionalidade de cancelamento de tarefas usando exceções. Se você captura exceções que não conhece, pode interferir no mecanismo de cancelamento da PPL.

Observação  Se você usa apenas token de cancelamento para sinalizar o cancelamento e não chama a função cancel_current_task, nenhuma é usada para o cancelamento.
 
Observação  Se uma continuação precisa verificar cancelamentos ou exceções que ocorreram em tarefas anteriores na cadeia de continuação, verifique se a continuação é baseada em tarefa.
 

Use padrões paralelos

Se o seu aplicativo executa computações pesadas, é muito provável que você precise usar técnicas de programação paralela. Há diversos padrões estabelecidos para usar de maneira efetiva o hardware de vários núcleos. A programação paralela com o Microsoft Visual C++ é um recurso de alguns dos padrões mais comuns, com exemplos que usam a PPL e a Biblioteca de agentes assíncronos.

Esteja ciente dos requisitos de teste especiais para operações assíncronas

Os testes de unidade exigem manipulação especial da sincronização quando há chamadas assíncronas. A maioria das estruturas de teste não permite que os testes esperem resultados assíncronos. Você precisa de uma codificação especial para contornar esse problema.

Além disso, ao testar componentes que exigem que algumas operações ocorram no thread principal, você precisa de uma forma de garantir que a estrutura de teste seja executada no contexto do thread necessário.

No Hilo, nós resolvemos esses dois problemas com um código de teste personalizado. Veja uma descrição do que fizemos em Testando o aplicativo neste guia.

Use computadores de estado finito para gerenciar operações intercaladas

Os programas assíncronos têm mais caminhos de execução possíveis e interações de recursos potenciais que os programas síncronos. Por exemplo, se o seu aplicativo permite que o usuário pressione o botão Voltar enquanto uma operação assíncrona, como o carregamento de um arquivo, está em andamento, isso deve ser considerado na lógica do aplicativo. Você pode cancelar a operação pendente e restaurar o estado do aplicativo ao que era antes do início da operação.

A possibilidade das operações do aplicativo serem intercaladas de maneira flexível ajuda a aumentar a percepção do usuário em relação à velocidade e à capacidade de resposta do aplicativo. Essa é uma característica importante dos aplicativos da Windows Store, mas se você não tiver cuidado, também pode ser uma fonte de bugs.

A manipulação correta dos vários caminhos de execução e interações possíveis com uma interface do usuário assíncrona exige técnicas de programação especiais. Os computadores de estado finito são uma boa opção, que pode ajudá-lo a implementar a manipulação robusta de operações intercaladas de operações assíncronas. Com um computador de estado finito, você descreve explicitamente o que acontece em cada situação possível.

Veja aqui um diagrama de um computador de estado finito do modelo de exibição do navegador de imagens do Hilo.

Computador de estado finito do modelo de exibição do navegador de imagens

No diagrama, cada oval representa um modo operacional distinto que uma instância da classe ImageBrowserViewModel pode ter no tempo de execução. Os arcos são transições nomeadas entre os modos. Algumas transições de estado ocorrem quando uma operação assíncrona termina. Você pode reconhecê-las porque elas contêm a palavra Finish em seus nomes. Todas as outras ocorrem quando uma operação assíncrona é iniciada.

O diagrama mostra o que pode acontecer em cada modo operacional. Por exemplo, se o navegador de imagens está no modo Running e ocorre uma ação OnNavigatedFrom, o modo resultante é Pending. As transições são resultado de

  • ações do usuário, como solicitações de navegação
  • ações do programa, como a conclusão de uma operação assíncrona iniciada anteriormente
  • eventos externos, como notificações de eventos de rede ou do sistema de arquivos

Nem todas as transições estão disponíveis em todos os modos. Por exemplo, a transição rotulada como FinishMonthAndYearQueries pode ocorrer somente no modo Running.

No código, as transições nomeadas são funções membro da classe ImageBrowserViewModel. O modo operacional atual do navegador de imagens é o valor da variável membro m_currentMode, que armazena valores da enumeração 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 */
};


Veja aqui um exemplo do código que implementa a transição ObserveFileChange no diagrama.

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


O método ObserveFileChange responde a notificações do sistema operacional de alterações externas nos arquivos de imagem que o usuário está vendo. Há quatro casos que devem ser considerados, dependendo do que está acontecendo no aplicativo no momento da notificação. Por exemplo, se o navegador de imagens está executando no momento uma consulta assíncrona, seu modo é Running. Nesse modo, a ação ObserveFileChange cancela a consulta em execução porque os resultados não são mais necessários e então inicia uma nova consulta. O modo operacional continua sendo Running.

[Início]

 

 

Mostrar:
© 2017 Microsoft