Programación asíncrona en C++ usando funciones reanudables y espera

Programación asíncrona en C++ usando funciones reanudables y espera

Como saben, recientemente lanzamos el Compilador de Visual C++ de noviembre de 2013 CTP. Una de las muchas características de este CTP es el soporte para funciones reanudables y en espera. En esta publicación de blog, quiero referirme a algunos ejemplos en los que estas características hacen que la experiencia de programación con API asíncrona sea mucho más simple.

Ejemplo 1

El primer ejemplo que vamos a ver es la muestra oficial del selector de archivos para Windows 8.1. Si abre la solución para este ejemplo con Visual Studio 2013, compila y ejecuta, verá una aplicación como la siguiente. Al seleccionar la opción 4 en este ejemplo, aparece el cuadro de diálogo del selector de archivos que le permite guardar un archivo de texto simple.

En el archivo de proyecto Scenario4.xaml.cpp, la función miembro "Scenario4::SaveFileButton_Click" contiene la implementación de mostrar el selector de archivos y escribir en la ubicación del archivo guardado. He eliminado algunos comentarios de código por razones de brevedad.

Código sin esperar:

void Scenario4::SaveFileButton_Click(Object^ sender, RoutedEventArgs^ e)
{
    rootPage->ResetScenarioOutput(OutputTextBlock);
 
    FileSavePicker^ savePicker = ref new FileSavePicker();
    savePicker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary;
 
    auto plainTextExtensions = ref new Platform::Collections::Vector<String^>();
    plainTextExtensions->Append(".txt");
    savePicker->FileTypeChoices->Insert("Plain Text", plainTextExtensions);
    savePicker->SuggestedFileName = "New Document";
 
    create_task(savePicker->PickSaveFileAsync()).then([this](StorageFile^ file)
    {
        if (file != nullptr)
        {
            CachedFileManager::DeferUpdates(file);
            create_task(FileIO::WriteTextAsync(file, file->Name)).then([this, file]()
            {
                create_task(CachedFileManager::CompleteUpdatesAsync(file)).then([this, file]

                    (FileUpdateStatus status)

                {
                    if (status == FileUpdateStatus::Complete)
                        OutputTextBlock->Text = "File " + file->Name + " was saved.";
                    else
                        OutputTextBlock->Text = "File " + file->Name + " couldn't be saved.";
                });
            });
        }
        else
        {
            OutputTextBlock->Text = "Operation cancelled.";
        }
    });
}

El código anterior usa Tareas PPL para llamar a la API asíncrona de Windows Runtime al proporcionar lambdas para manejar los resultados de esas API.

Hagamos algunos cambios en este código ahora:

  • Supondré que ya ha descargado e instalado el CTP de noviembre.
  • En las propiedades del proyecto, cambie el conjunto de herramientas de la plataforma a "Visual C++ Compiler Nov 2013 CTP (CTP_Nov2013)
  • Abra el archivo Scenario4.xaml.h y a la clase "Scenario4", agregue una función privada con la siguiente firma:
void SaveFileButtonWithAwait() __resumable;
  • Abra el archivo Scenario4.xaml.cpp y, debajo de las declaraciones de inclusión existentes, agregue lo siguiente:
#include <pplawait.h>
  • En el mismo archivo, vaya a la función miembro existente "Scenario4::SaveFileButton_Click" y comente todo su contenido. En su lugar, agregue una simple llamada a la función miembro recién agregada:
SaveFileButtonWithAwait();
  • Proporcione la implementación de la función miembro que agregamos anteriormente al archivo de encabezado. El código se ve así:

Código con espera:

void Scenario4::SaveFileButtonWithAwait() __resumable
{
    rootPage->ResetScenarioOutput(OutputTextBlock);
 
    FileSavePicker^ savePicker = ref new FileSavePicker();
    savePicker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary;
 
    auto plainTextExtensions = ref new Platform::Collections::Vector<String^>();
    plainTextExtensions->Append(".txt");
    savePicker->FileTypeChoices->Insert("Plain Text", plainTextExtensions);
    savePicker->SuggestedFileName = "New Document";
 
    auto file = __await savePicker->PickSaveFileAsync();
    if (file != nullptr)
    {
        CachedFileManager::DeferUpdates(file);
        __await FileIO::WriteTextAsync(file, file->Name);
        auto status = __await CachedFileManager::CompleteUpdatesAsync(file);
        if (status == FileUpdateStatus::Complete)
        {
            OutputTextBlock->Text = "File " + file->Name + " was saved.";
        }
        else
        {
            OutputTextBlock->Text = "File " + file->Name + " couldn't be saved.";
        }
    }
    else
    {
        OutputTextBlock->Text = "Operation cancelled.";
    }
}

El código anterior usa await para, bueno, esperar el resultado de la API asíncrona. Si contrasta este código (usando await) con el código anterior (usando tareas de PPL), estará de acuerdo en que mientras ambos hacen el trabajo, el último es definitivamente mejor.

Ejemplo 2

Otro ejemplo (no presente como una muestra en línea pero que se usa en una aplicación real) es el siguiente código. Básicamente, llama a la API FilePicker de Windows Runtime para seleccionar varias imágenes y luego crea varias tareas para copiar todos los archivos seleccionados en la carpeta temporal de la aplicación. Antes de continuar, debe esperar hasta que se copien todos los archivos.

Código sin esperar:

void XamlSpiro::MainPage::loadImagesWithPPL()
{
    auto openPicker = ref new FileOpenPicker();
    openPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary;
    openPicker->ViewMode = PickerViewMode::Thumbnail;
    openPicker->FileTypeFilter->Append("*");
 
    task<IVectorView<StorageFile^>^>(openPicker->PickMultipleFilesAsync()).then([this]

        (IVectorView<StorageFile^>^ fileVector)

    {
        for (auto file : fileVector)
        {
            m_copyTasks.push_back(create_task(file->CopyAsync(

                ApplicationData::Current->TemporaryFolder,

                file->Name, NameCollisionOption::ReplaceExisting)));

        }
 
        when_all(begin(m_copyTasks), end(m_copyTasks)).then([this](std::vector<StorageFile^> results)
        {
            for (auto copiedFile : results)
            {
                InputFilesVector->Append(copiedFile);
            }
        }).then([this]()
        {
            DisplayImages();
        });
    });
}

Código con espera:

void XamlSpiro::MainPage::loadImagesWithAwait() __resumable
{
    auto openPicker = ref new FileOpenPicker();
    openPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary;
    openPicker->ViewMode = PickerViewMode::Thumbnail;
    openPicker->FileTypeFilter->Append("*");
 
    auto fileVector = __await openPicker->PickMultipleFilesAsync();
 
    for (auto file : fileVector)
    {
        m_copyTasks.push_back(create_task(file->CopyAsync(ApplicationData::Current->TemporaryFolder,

            file->Name, NameCollisionOption::ReplaceExisting)));

    }
 
    auto results = __await when_all(begin(m_copyTasks), end(m_copyTasks));
    for (auto copiedFile : results)
    {
        InputFilesVector->Append(copiedFile);
    }
    DisplayImages();
}

Una diferencia sutil en este caso es que no estamos llamando innecesariamente await para cada llamada de CopyAsync. Eso habría sido subóptimo. En su lugar, todavía estamos creando tareas individuales para todas las operaciones de copia y llamando await solo en la operación when_all para que esperemos solo la cantidad de tiempo requerida, ni más ni menos.

Como sabrá, el mundo de la Tienda Windows está lleno de API asíncronas de Windows Runtime. Por lo tanto, estas características son especialmente útiles para el desarrollo de aplicaciones de la Tienda. Proporcionan una forma síncrona de pensar en el código que necesita componer llamadas asíncronas. Esperamos que pruebe estas funciones y nos envíe sus comentarios.