Tworzenie operacji asynchronicznych w języku C++ dla aplikacji platformy uniwersalnej systemu Windows

W tym dokumencie opisano niektóre kluczowe kwestie, które należy wziąć pod uwagę podczas używania klasy zadań do tworzenia operacji asynchronicznych opartych na puli wątków systemu Windows w aplikacji uniwersalnej środowisko wykonawcze systemu Windows (UWP).

Użycie programowania asynchronicznego jest kluczowym składnikiem w modelu aplikacji środowisko wykonawcze systemu Windows, ponieważ umożliwia aplikacjom reagowanie na dane wejściowe użytkownika. Długotrwałe zadanie można uruchomić bez blokowania wątku interfejsu użytkownika i później można uzyskać wyniki zadania. Możesz również anulować zadania i otrzymywać powiadomienia o postępie, gdy zadania są uruchamiane w tle. Dokument Programowanie asynchroniczne w języku C++ zawiera omówienie asynchronicznego wzorca dostępnego w języku Visual C++ w celu tworzenia aplikacji platformy UWP. W tym dokumencie pokazano, jak używać i tworzyć łańcuchy operacji asynchronicznych środowisko wykonawcze systemu Windows. W tej sekcji opisano sposób używania typów w pliku ppltasks.h do tworzenia operacji asynchronicznych, które mogą być używane przez inny składnik środowisko wykonawcze systemu Windows i jak kontrolować sposób wykonywania pracy asynchronicznej. Rozważ również przeczytanie wzorców programowania asynchronicznego i wskazówek w witrynie Hilo (aplikacje ze Sklepu Windows przy użyciu języka C++ i XAML), aby dowiedzieć się, jak używaliśmy klasy zadań do implementowania operacji asynchronicznych w hilo, aplikacji środowisko wykonawcze systemu Windows przy użyciu języka C++ i XAML.

Uwaga

Bibliotekę równoległych wzorców (PPL) i bibliotekę agentów asynchronicznych można używać w aplikacji platformy UWP. Nie można jednak użyć harmonogramu zadań ani menedżera zasobów. W tym dokumencie opisano dodatkowe funkcje udostępniane przez PPL dostępne tylko dla aplikacji platformy UWP, a nie dla aplikacji klasycznej.

Kwestie kluczowe

  • Użyj współbieżności::create_async , aby utworzyć operacje asynchroniczne, które mogą być używane przez inne składniki (które mogą być zapisywane w językach innych niż C++).

  • Użyj współbieżności::p rogress_reporter , aby zgłosić powiadomienia o postępie do składników wywołujących operacje asynchroniczne.

  • Użyj tokenów anulowania, aby włączyć wewnętrzne operacje asynchroniczne do anulowania.

  • Zachowanie create_async funkcji zależy od zwracanego typu funkcji pracy, która jest do niej przekazywana. Funkcja pracy zwracająca zadanie ( task<T> lub task<void>) jest uruchamiana synchronicznie w kontekście o nazwie create_async. Funkcja pracy zwracająca T lub void uruchamiana w dowolnym kontekście.

  • Możesz użyć metody concurrency::task::then , aby utworzyć łańcuch zadań, które są uruchamiane po drugiej. W aplikacji platformy UWP domyślny kontekst kontynuacji zadania zależy od sposobu konstruowania tego zadania. Jeśli zadanie zostało utworzone przez przekazanie akcji asynchronicznej do konstruktora zadania lub przekazanie wyrażenia lambda zwracającego akcję asynchroniczną, domyślny kontekst dla wszystkich kontynuacji tego zadania jest bieżącym kontekstem. Jeśli zadanie nie jest konstruowane z akcji asynchronicznej, domyślnie do kontynuacji zadania jest używany dowolny kontekst. Domyślny kontekst można zastąpić za pomocą klasy concurrency::task_continuation_context .

W tym dokumencie

Tworzenie operacji asynchronicznych

Możesz użyć modelu zadań i kontynuacji w bibliotece wzorców równoległych (PPL), aby zdefiniować zadania w tle, a także dodatkowe zadania uruchamiane po zakończeniu poprzedniego zadania. Ta funkcja jest udostępniana przez klasę concurrency::task . Aby uzyskać więcej informacji na temat tego modelu i task klasy, zobacz Równoległość zadań.

Środowisko wykonawcze systemu Windows to interfejs programowania, którego można użyć do tworzenia aplikacji platformy UNIWERSALNEJ systemu Windows, które działają tylko w specjalnym środowisku systemu operacyjnego. Takie aplikacje używają autoryzowanych funkcji, typów danych i urządzeń oraz są dystrybuowane ze Sklepu Microsoft. Środowisko wykonawcze systemu Windows jest reprezentowana przez interfejs binarny aplikacji (ABI). ABI to podstawowy kontrakt binarny, który udostępnia interfejsy API środowisko wykonawcze systemu Windows dla języków programowania, takich jak Visual C++.

Korzystając z środowisko wykonawcze systemu Windows, możesz użyć najlepszych funkcji różnych języków programowania i połączyć je w jedną aplikację. Możesz na przykład utworzyć interfejs użytkownika w języku JavaScript i wykonać logikę aplikacji intensywnie korzystającej z obliczeń w składniku języka C++. Możliwość wykonywania tych operacji intensywnie korzystających z obliczeń w tle jest kluczowym czynnikiem w utrzymaniu reakcji interfejsu użytkownika. task Ponieważ klasa jest specyficzna dla języka C++, należy użyć interfejsu środowisko wykonawcze systemu Windows do komunikowania operacji asynchronicznych z innymi składnikami (które mogą być napisane w językach innych niż C++). Środowisko wykonawcze systemu Windows udostępnia cztery interfejsy, których można użyć do reprezentowania operacji asynchronicznych:

Windows::Foundation::IAsyncAction
Reprezentuje akcję asynchroniczną.

Windows::Foundation::IAsyncActionWithProgress TProgress<>
Reprezentuje akcję asynchroniczną, która zgłasza postęp.

Windows::Foundation::IAsyncOperation<TResult>
Reprezentuje operację asynchroniczną, która zwraca wynik.

Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Reprezentuje operację asynchroniczną, która zwraca wynik i zgłasza postęp.

Pojęcie akcji oznacza, że zadanie asynchroniczne nie generuje wartości (pomyśl o funkcji zwracającej voidwartość ). Pojęcie operacji oznacza, że zadanie asynchroniczne generuje wartość. Pojęcie postępu oznacza, że zadanie może zgłaszać komunikaty postępu do obiektu wywołującego. Języki JavaScript, .NET Framework i Visual C++ zapewniają własny sposób tworzenia wystąpień tych interfejsów do użycia w granicach usługi ABI. W przypadku języka Visual C++PPL udostępnia funkcję concurrency::create_async . Ta funkcja tworzy środowisko wykonawcze systemu Windows asynchroniczną akcję lub operację reprezentującą ukończenie zadania. Funkcja create_async przyjmuje funkcję pracy (zazwyczaj wyrażenie lambda), wewnętrznie tworzy task obiekt i opakowuje to zadanie w jednym z czterech asynchronicznych interfejsów środowisko wykonawcze systemu Windows.

Uwaga

Użyj create_async tylko wtedy, gdy musisz utworzyć funkcje, do których można uzyskać dostęp z innego języka lub innego składnika środowisko wykonawcze systemu Windows. task Użyj klasy bezpośrednio, gdy wiadomo, że operacja jest zarówno utworzona, jak i użyta przez kod języka C++ w tym samym składniku.

Zwracany create_async typ klasy jest określany przez typ argumentów. Jeśli na przykład funkcja pracy nie zwraca wartości i nie zgłasza postępu, create_async zwraca wartość IAsyncAction. Jeśli funkcja pracy nie zwraca wartości, a także zgłasza postęp, create_async zwraca wartość IAsyncActionWithProgress. Aby zgłosić postęp, podaj obiekt concurrency::p rogress_reporter jako parametr funkcji pracy. Możliwość raportowania postępu umożliwia raportowanie ilości wykonanej pracy i ilość nadal pozostaje (na przykład jako wartość procentowa). Umożliwia również raportowanie wyników w miarę ich dostępności.

Interfejsy IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult>i IAsyncActionOperationWithProgress<TProgress, TProgress> zapewniają metodę umożliwiającą Cancel anulowanie operacji asynchronicznej. Klasa task działa z tokenami anulowania. Jeśli używasz tokenu anulowania do anulowania pracy, środowisko uruchomieniowe nie uruchamia nowej pracy, która subskrybuje ten token. Praca, która jest już aktywna, może monitorować token anulowania i zatrzymywać, kiedy to możliwe. Ten mechanizm został szczegółowo opisany w dokumencie Anulowanie w PPL. Anulowanie zadania można połączyć za pomocą metod środowisko wykonawcze systemu Windows Cancel na dwa sposoby. Najpierw można zdefiniować funkcję pracy przekazywaną do create_async wykonywania współbieżności::cancellation_token obiektu. Po wywołaniu Cancel metody ten token anulowania jest anulowany, a normalne reguły anulowania mają zastosowanie do bazowego task obiektu obsługującego create_async wywołanie. Jeśli nie podasz cancellation_token obiektu, obiekt źródłowy task definiuje go niejawnie. Zdefiniuj cancellation_token obiekt, gdy musisz wspólnie reagować na anulowanie w funkcji pracy. Przykład sekcji: Kontrolowanie wykonywania w aplikacji środowisko wykonawcze systemu Windows przy użyciu języka C++ i XAML przedstawia przykład sposobu przeprowadzania anulowania w aplikacji platforma uniwersalna systemu Windows (UWP) przy użyciu języka C# i XAML korzystającego z niestandardowego kodu środowisko wykonawcze systemu Windows składnik języka C++.

Ostrzeżenie

W łańcuchu kontynuacji zadań zawsze należy wyczyścić stan, a następnie wywołać współbieżność::cancel_current_task po anulowaniu tokenu anulowania. Jeśli zwracasz wcześniej zamiast wywoływać cancel_current_taskmetodę , operacja przechodzi do stanu ukończonego zamiast stanu anulowanego.

W poniższej tabeli podsumowano kombinacje, których można użyć do zdefiniowania operacji asynchronicznych w aplikacji.

Aby utworzyć ten interfejs środowisko wykonawcze systemu Windows Zwróć ten typ z create_async Przekaż te typy parametrów do funkcji roboczej, aby użyć niejawnego tokenu anulowania Przekaż te typy parametrów do funkcji roboczej, aby użyć jawnego tokenu anulowania
IAsyncAction void lub task<void> (brak) (cancellation_token)
IAsyncActionWithProgress<TProgress> void lub task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T lub task<T> (brak) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T lub task<T> (progress_reporter) (progress_reporter, cancellation_token)

Wartość lub task obiekt można zwrócić z funkcji roboczej przekazanej create_async do funkcji . Te odmiany generują różne zachowania. Gdy zwracasz wartość, funkcja pracy jest owinięta w obiekcie task , aby można było ją uruchomić w wątku w tle. Ponadto bazowe task używa niejawnego tokenu anulowania. Z drugiej strony, jeśli zwracasz task obiekt, funkcja robocza jest uruchamiana synchronicznie. W związku z tym, jeśli zwracasz task obiekt, upewnij się, że wszystkie długie operacje w funkcji roboczej są również uruchamiane jako zadania, aby umożliwić aplikacji zachowanie reakcji. Ponadto bazowe task nie używa niejawnego tokenu anulowania. W związku z tym należy zdefiniować funkcję roboczą cancellation_token , aby podjąć obiekt, jeśli potrzebujesz obsługi anulowania podczas zwracania task obiektu z create_asyncobiektu .

W poniższym przykładzie przedstawiono różne sposoby tworzenia IAsyncAction obiektu, który może być używany przez inny składnik środowisko wykonawcze systemu Windows.

// Creates an IAsyncAction object and uses an implicit cancellation token.
auto op1 = create_async([]
{
    // Define work here.
});

// Creates an IAsyncAction object and uses no cancellation token.
auto op2 = create_async([]
{
    return create_task([]
    {
        // Define work here.
    });
});

// Creates an IAsyncAction object and uses an explicit cancellation token.
auto op3 = create_async([](cancellation_token ct)
{
    // Define work here.
});

// Creates an IAsyncAction object that runs another task and also uses an explicit cancellation token.
auto op4 = create_async([](cancellation_token ct)
{
    return create_task([ct]()
    {
        // Define work here.
    });
});

Przykład: tworzenie składnika środowisko wykonawcze systemu Windows języka C++ i korzystanie z niego z języka C#

Rozważmy aplikację, która używa języka XAML i C# do definiowania interfejsu użytkownika i składnika środowisko wykonawcze systemu Windows języka C++ w celu wykonywania operacji intensywnie korzystających z obliczeń. W tym przykładzie składnik języka C++ oblicza liczby w danym zakresie jako podstawowe. Aby zilustrować różnice między czterema interfejsami zadań asynchronicznych środowisko wykonawcze systemu Windows, uruchom polecenie w programie Visual Studio, tworząc puste rozwiązanie i nadając mu Primesnazwę . Następnie dodaj do rozwiązania projekt środowisko wykonawcze systemu Windows Component i nadaj mu PrimesLibrarynazwę . Dodaj następujący kod do wygenerowanego pliku nagłówka języka C++ (w tym przykładzie zmieniono nazwę klasy Class1.h na Primes.h). Każda public metoda definiuje jeden z czterech interfejsów asynchronicznych. Metody zwracające wartość zwracają obiekt Windows::Foundation::Collections::IVector<int> . Metody, które zgłaszają postęp, generują double wartości, które definiują procent ogólnej pracy, która została ukończona.

#pragma once

namespace PrimesLibrary
{
    public ref class Primes sealed
    {
    public:
        Primes();

        // Computes the numbers that are prime in the provided range and stores them in an internal variable.
        Windows::Foundation::IAsyncAction^ ComputePrimesAsync(int first, int last);

        // Computes the numbers that are prime in the provided range and stores them in an internal variable.
        // This version also reports progress messages.
        Windows::Foundation::IAsyncActionWithProgress<double>^ ComputePrimesWithProgressAsync(int first, int last);

        // Gets the numbers that are prime in the provided range.
        Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<int>^>^ GetPrimesAsync(int first, int last);

        // Gets the numbers that are prime in the provided range. This version also reports progress messages.
        Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^ GetPrimesWithProgressAsync(int first, int last);
    };
}

Uwaga

Zgodnie z konwencją nazwy metod asynchronicznych w środowisko wykonawcze systemu Windows zwykle kończą się ciągiem "Async".

Dodaj następujący kod do wygenerowanego pliku źródłowego C++ (w tym przykładzie zmieniono nazwę klasy Class1.cpp na Primes.cpp). Funkcja is_prime określa, czy jego dane wejściowe są prime. Pozostałe metody implementują klasę Primes . Każde wywołanie używa create_async podpisu zgodnego z metodą, z której jest wywoływana. Na przykład, ponieważ Primes::ComputePrimesAsync zwraca IAsyncActionwartość , funkcja pracy podana do create_async polecenia nie zwraca wartości i nie pobiera progress_reporter obiektu jako parametru.

// PrimesLibrary.cpp
#include "pch.h"
#include "Primes.h"
#include <atomic>
#include <collection.h>
#include <ppltasks.h>
#include <concurrent_vector.h>

using namespace concurrency;
using namespace std;

using namespace Platform;
using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;

using namespace PrimesLibrary;

Primes::Primes()
{
}

// Determines whether the input value is prime. 
bool is_prime(int n)
{
    if (n < 2)
    {
        return false;
    }
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
        {
            return false;
        }
    }
    return true;
}

// Adds the numbers that are prime in the provided range  
// to the primes global variable.
IAsyncAction^ Primes::ComputePrimesAsync(int first, int last)
{
    return create_async([this, first, last]
    {
        // Ensure that the input values are in range. 
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        parallel_for(first, last + 1, [this](int n)
        {
            if (is_prime(n))
            {
                // Perhaps store the value somewhere...
            }
        });
    });
}

IAsyncActionWithProgress<double>^ Primes::ComputePrimesWithProgressAsync(int first, int last)
{
    return create_async([first, last](progress_reporter<double> reporter)
    {
        // Ensure that the input values are in range.
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel. 
        atomic<long> operation = 0;
        long range = last - first + 1;
        double lastPercent = 0.0;
        parallel_for(first, last + 1, [&operation, range, &lastPercent, reporter](int n)
        {
            // Report progress message.
            double progress = 100.0 * (++operation) / range;
            if (progress >= lastPercent)
            {
                reporter.report(progress);
                lastPercent += 1.0;
            }

            if (is_prime(n))
            {
                // Perhaps store the value somewhere...
            }
        });
        reporter.report(100.0);
    });
}

IAsyncOperation<IVector<int>^>^ Primes::GetPrimesAsync(int first, int last)
{
    return create_async([this, first, last]() -> IVector<int>^
    {
        // Ensure that the input values are in range. 
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        concurrent_vector<int> primes;
        parallel_for(first, last + 1, [this, &primes](int n)
        {
            // If the value is prime, add it to the global vector.
            if (is_prime(n))
            {
                primes.push_back(n);
            }
        });
        // Sort the results.
        sort(begin(primes), end(primes), less<int>());

        // Copy the results to an IVector object. The IVector 
        // interface makes collections of data available to other 
        // Windows Runtime components.
        auto results = ref new Vector<int>();
        for (int prime : primes)
        {
            results->Append(prime);
        }
        return results;
    });
}

IAsyncOperationWithProgress<IVector<int>^, double>^ Primes::GetPrimesWithProgressAsync(int first, int last)
{
    return create_async([this, first, last](progress_reporter<double> reporter) -> IVector<int>^
    {
        // Ensure that the input values are in range.
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        concurrent_vector<int> primes;
        long operation = 0;
        long range = last - first + 1;
        double lastPercent = 0.0;
        parallel_for(first, last + 1, [&primes, &operation, range, &lastPercent, reporter](int n)
        {
            // Report progress message.
            double progress = 100.0 * (++operation) / range;
            if (progress >= lastPercent)
            {
                reporter.report(progress);
                lastPercent += 1.0;
            }

            // If the value is prime, add it to the local vector. 
            if (is_prime(n))
            {
                primes.push_back(n);
            }
        });
        reporter.report(100.0);

        // Sort the results.
        sort(begin(primes), end(primes), less<int>());

        // Copy the results to an IVector object. The IVector 
        // interface makes collections of data available to other 
        // Windows Runtime components.
        auto results = ref new Vector<int>();
        for (int prime : primes)
        {
            results->Append(prime);
        }
        return results;
    });
}

Każda metoda najpierw przeprowadza walidację, aby upewnić się, że parametry wejściowe nie są ujemne. Jeśli wartość wejściowa jest ujemna, metoda zgłasza wyjątek Platform::InvalidArgumentException. Obsługa błędów wyjaśniono w dalszej części tej sekcji.

Aby korzystać z tych metod z aplikacji platformy UWP, użyj szablonu Pusta aplikacja języka Visual C# (XAML), aby dodać drugi projekt do rozwiązania programu Visual Studio. W tym przykładzie nazwa projektu Primes. Następnie z Primes projektu dodaj odwołanie do PrimesLibrary projektu.

Dodaj następujący kod do pliku MainPage.xaml. Ten kod definiuje interfejs użytkownika, aby można było wywołać składnik C++ i wyświetlić wyniki.

<Page
    x:Class="Primes.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Primes"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="300"/>
            <ColumnDefinition Width="300"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="125"/>
            <RowDefinition Height="125"/>
            <RowDefinition Height="125"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Column="0" Grid.Row="0">
            <Button Name="b1" Click="computePrimes">Compute Primes</Button>
            <TextBlock Name="tb1"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="0">
            <Button Name="b2" Click="computePrimesWithProgress">Compute Primes with Progress</Button>
            <ProgressBar Name="pb1" HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb2"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="0" Grid.Row="1">
            <Button Name="b3" Click="getPrimes">Get Primes</Button>
            <TextBlock Name="tb3"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="1">
            <Button Name="b4" Click="getPrimesWithProgress">Get Primes with Progress</Button>
            <ProgressBar Name="pb4"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb4"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="0" Grid.Row="2">
            <Button Name="b5" Click="getPrimesHandleErrors">Get Primes and Handle Errors</Button>
            <ProgressBar Name="pb5"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb5"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="2">
            <Button Name="b6" Click="getPrimesCancellation">Get Primes with Cancellation</Button>
            <Button Name="cancelButton" Click="cancelGetPrimes" IsEnabled="false">Cancel</Button>
            <ProgressBar Name="pb6"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb6"></TextBlock>
        </StackPanel>
    </Grid>
</Page>

Dodaj następujący kod do klasy w pliku MainPage MainPage.xaml. Ten kod definiuje Primes obiekt i programy obsługi zdarzeń przycisku.

private PrimesLibrary.Primes primesLib = new PrimesLibrary.Primes();

private async void computePrimes(object sender, RoutedEventArgs e)
{
    b1.IsEnabled = false;
    tb1.Text = "Working...";

    var asyncAction = primesLib.ComputePrimesAsync(0, 100000);

    await asyncAction;

    tb1.Text = "Done";
    b1.IsEnabled = true;
}

private async void computePrimesWithProgress(object sender, RoutedEventArgs e)
{
    b2.IsEnabled = false;
    tb2.Text = "Working...";

    var asyncAction = primesLib.ComputePrimesWithProgressAsync(0, 100000);
    asyncAction.Progress = new AsyncActionProgressHandler<double>((action, progress) =>
    {
        pb1.Value = progress;
    });

    await asyncAction;

    tb2.Text = "Done";
    b2.IsEnabled = true;
}

private async void getPrimes(object sender, RoutedEventArgs e)
{
    b3.IsEnabled = false;
    tb3.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesAsync(0, 100000);

    await asyncOperation;

    tb3.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    b3.IsEnabled = true;
}

private async void getPrimesWithProgress(object sender, RoutedEventArgs e)
{
    b4.IsEnabled = false;
    tb4.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesWithProgressAsync(0, 100000);
    asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb4.Value = progress;
    });

    await asyncOperation;

    tb4.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    b4.IsEnabled = true;
}

private async void getPrimesHandleErrors(object sender, RoutedEventArgs e)
{
    b5.IsEnabled = false;
    tb5.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesWithProgressAsync(-1000, 100000);
    asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb5.Value = progress;
    });

    try
    {
        await asyncOperation;
        tb5.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    }
    catch (ArgumentException ex)
    {
        tb5.Text = "ERROR: " + ex.Message;
    }

    b5.IsEnabled = true;
}

private IAsyncOperationWithProgress<IList<int>, double> asyncCancelableOperation;

private async void getPrimesCancellation(object sender, RoutedEventArgs e)
{
    b6.IsEnabled = false;
    cancelButton.IsEnabled = true;
    tb6.Text = "Working...";

    asyncCancelableOperation = primesLib.GetPrimesWithProgressAsync(0, 200000);
    asyncCancelableOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb6.Value = progress;
    });

    try
    {
        await asyncCancelableOperation;
        tb6.Text = "Found " + asyncCancelableOperation.GetResults().Count + " primes";
    }
    catch (System.Threading.Tasks.TaskCanceledException)
    {
        tb6.Text = "Operation canceled";
    }

    b6.IsEnabled = true;
    cancelButton.IsEnabled = false;
}

private void cancelGetPrimes(object sender, RoutedEventArgs e)
{
    cancelButton.IsEnabled = false;
    asyncCancelableOperation.Cancel();
}

Te metody używają async słów kluczowych i await , aby zaktualizować interfejs użytkownika po zakończeniu operacji asynchronicznych. Aby uzyskać informacje na temat asynchronicznego kodowania w aplikacjach platformy UWP, zobacz Wątkowanie i programowanie asynchroniczne.

Metody getPrimesCancellation i cancelGetPrimes współpracują ze sobą, aby umożliwić użytkownikowi anulowanie operacji. Gdy użytkownik wybierze przycisk Anuluj, metoda wywołuje metodę IAsyncOperationWithProgress<TResult, TProgress>::Anuluj, aby anulować cancelGetPrimes operację. Środowisko uruchomieniowe współbieżności, które zarządza podstawową operacją asynchroniczną, zgłasza wewnętrzny typ wyjątku przechwycony przez środowisko wykonawcze systemu Windows w celu przekazania, że anulowanie zostało zakończone. Aby uzyskać więcej informacji na temat modelu anulowania, zobacz Anulowanie.

Ważne

Aby umożliwić użytkownikowi PPL poprawne raportowanie do środowisko wykonawcze systemu Windows, że operacja została anulowana, nie przechwyć tego typu wyjątku wewnętrznego. Oznacza to, że nie należy również przechwytywać wszystkich wyjątków (catch (...)). Jeśli musisz przechwycić wszystkie wyjątki, wróć ponownie wyjątek, aby upewnić się, że środowisko wykonawcze systemu Windows może zakończyć operację anulowania.

Poniższa ilustracja przedstawia aplikację po wybraniu Primes każdej opcji.

Windows Runtime Primes app.

Przykład używany create_async do tworzenia zadań asynchronicznych, które mogą być używane przez inne języki, zobacz Używanie języka C++ w przykładzie Bing Mapy Trip Optimizer.

Kontrolowanie wątku wykonywania

W środowisko wykonawcze systemu Windows jest używany model wątkowania COM. W tym modelu obiekty są hostowane w różnych mieszkaniach, w zależności od sposobu ich obsługi synchronizacji. Obiekty bezpieczne wątkowo są hostowane w mieszkaniu wielowątkowy (MTA). Obiekty, do których musi uzyskiwać dostęp pojedynczy wątek, są hostowane w mieszkaniu jednowątkowym (STA).

W aplikacji, która ma interfejs użytkownika, wątek ASTA (Application STA) jest odpowiedzialny za komunikaty okna pompowania i jest jedynym wątkiem w procesie, który może zaktualizować kontrolki interfejsu użytkownika hostowanego przez sta. Ma to dwie konsekwencje. Najpierw, aby umożliwić aplikacji zachowanie reakcji, wszystkie operacje intensywnie korzystające z procesora CPU i we/wy nie powinny być uruchamiane w wątku ASTA. Po drugie, wyniki pochodzące z wątków w tle muszą być marshalowane z powrotem do usługi ASTA, aby zaktualizować interfejs użytkownika. W aplikacji MainPage platformy UWP języka C++ i innych stronach XAML wszystkie są uruchamiane w usłudze ATSA. W związku z tym kontynuacje zadań zadeklarowane na platformie ASTA są uruchamiane domyślnie, dzięki czemu można aktualizować kontrolki bezpośrednio w treści kontynuacji. Jeśli jednak zagnieżdżasz zadanie w innym zadaniu, wszelkie kontynuacje tego zagnieżdżonego zadania są uruchamiane w usłudze MTA. W związku z tym należy rozważyć, czy jawnie określić kontekst, w którym są uruchamiane te kontynuacje.

Zadanie utworzone na podstawie operacji asynchronicznej, takiej jak IAsyncOperation<TResult>, używa specjalnej semantyki, które mogą pomóc w ignorowaniu szczegółów wątków. Mimo że operacja może być uruchamiana na wątku w tle (lub nie może być w ogóle wspierana przez wątek), jego kontynuacje są domyślnie gwarantowane do uruchomienia w mieszkaniu, które rozpoczęło operacje kontynuacji (innymi słowy, z mieszkania, które nazywało task::then). Możesz użyć klasy concurrency::task_continuation_context , aby kontrolować kontekst wykonywania kontynuacji. Użyj tych statycznych metod pomocnika, aby utworzyć task_continuation_context obiekty:

Obiekt można przekazać task_continuation_context do metody task::then , aby jawnie kontrolować kontekst wykonywania kontynuacji lub przekazać zadanie do innego mieszkania, a następnie wywołać task::then metodę, aby niejawnie kontrolować kontekst wykonywania.

Ważne

Ponieważ główny wątek interfejsu użytkownika aplikacji platformy UWP działa w obszarze STA, kontynuacje tworzone na tym sta domyślnie są uruchamiane w sta. W związku z tym kontynuacje tworzone w usłudze MTA są uruchamiane w usłudze MTA.

W poniższej sekcji przedstawiono aplikację, która odczytuje plik z dysku, znajduje najbardziej typowe słowa w tym pliku, a następnie wyświetla wyniki w interfejsie użytkownika. Ostateczna operacja, aktualizowanie interfejsu użytkownika, odbywa się w wątku interfejsu użytkownika.

Ważne

To zachowanie jest specyficzne dla aplikacji platformy UWP. W przypadku aplikacji klasycznych nie kontrolujesz miejsca uruchamiania kontynuacji. Zamiast tego harmonogram wybiera wątek procesu roboczego, na którym ma być uruchamiana każda kontynuacja.

Ważne

Nie należy wywoływać współbieżności::task::wait w treści kontynuacji uruchamianej w stacie. W przeciwnym razie środowisko uruchomieniowe zgłasza współbieżność::invalid_operation , ponieważ ta metoda blokuje bieżący wątek i może spowodować, że aplikacja przestanie odpowiadać. Można jednak wywołać metodę concurrency::task::get , aby otrzymać wynik zadania przedzibowego w kontynuacji opartej na zadaniach.

Przykład: kontrolowanie wykonywania w aplikacji środowisko wykonawcze systemu Windows przy użyciu języka C++ i XAML

Rozważmy aplikację XAML języka C++, która odczytuje plik z dysku, znajduje najbardziej typowe słowa w tym pliku, a następnie wyświetla wyniki w interfejsie użytkownika. Aby utworzyć tę aplikację, uruchom polecenie w programie Visual Studio, tworząc projekt Pusta aplikacja (uniwersalny system Windows) i nadając mu CommonWordsnazwę . W manifeście aplikacji określ funkcję Biblioteka dokumentów, aby umożliwić aplikacji dostęp do folderu Dokumenty. Dodaj również typ pliku Text (txt) do sekcji deklaracji manifestu aplikacji. Aby uzyskać więcej informacji na temat możliwości i deklaracji aplikacji, zobacz Tworzenie pakietów, wdrażanie i wykonywanie zapytań dotyczących aplikacji systemu Windows.

Grid Zaktualizuj element w pliku MainPage.xaml, aby zawierał ProgressRing element i TextBlock element. Wskazuje ProgressRing , że operacja jest w toku i TextBlock pokazuje wyniki obliczeń.

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ProgressRing x:Name="Progress"/>
    <TextBlock x:Name="Results" FontSize="16"/>
</Grid>

Dodaj następujące #include instrukcje do pliku pch.h.

#include <sstream>
#include <ppltasks.h>
#include <concurrent_unordered_map.h>

Dodaj następujące deklaracje metody do MainPage klasy (MainPage.h).

private:
    // Splits the provided text string into individual words.
    concurrency::task<std::vector<std::wstring>> MakeWordList(Platform::String^ text);

    // Finds the most common words that are at least the provided minimum length.
    concurrency::task<std::vector<std::pair<std::wstring, size_t>>> FindCommonWords(const std::vector<std::wstring>& words, size_t min_length, size_t count);

    // Shows the most common words on the UI.
    void ShowResults(const std::vector<std::pair<std::wstring, size_t>>& commonWords);

Dodaj następujące using instrukcje do pliku MainPage.cpp.

using namespace concurrency;
using namespace std;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;

W pliku MainPage.cpp zaimplementuj MainPage::MakeWordListmetody , MainPage::FindCommonWordsi MainPage::ShowResults . Operacje MainPage::MakeWordList i MainPage::FindCommonWords wykonują operacje intensywnie korzystające z obliczeń. Metoda MainPage::ShowResults wyświetla wynik obliczeń w interfejsie użytkownika.

// Splits the provided text string into individual words.
task<vector<wstring>> MainPage::MakeWordList(String^ text)
{
    return create_task([text]() -> vector<wstring>
    {
        vector<wstring> words;

        // Add continuous sequences of alphanumeric characters to the string vector.
        wstring current_word;
        for (wchar_t ch : text)
        {
            if (!iswalnum(ch))
            {
                if (current_word.length() > 0)
                {
                    words.push_back(current_word);
                    current_word.clear();
                }
            }
            else
            {
                current_word += ch;
            }
        }

        return words;
    });
}

// Finds the most common words that are at least the provided minimum length.
task<vector<pair<wstring, size_t>>> MainPage::FindCommonWords(const vector<wstring>& words, size_t min_length, size_t count)
{
    return create_task([words, min_length, count]() -> vector<pair<wstring, size_t>>
    {
        typedef pair<wstring, size_t> pair;

        // Counts the occurrences of each word.
        concurrent_unordered_map<wstring, size_t> counts;

        parallel_for_each(begin(words), end(words), [&counts, min_length](const wstring& word)
        {
            // Increment the count of words that are at least the minimum length. 
            if (word.length() >= min_length)
            {
                // Increment the count.
                InterlockedIncrement(&counts[word]);
            }
        });

        // Copy the contents of the map to a vector and sort the vector by the number of occurrences of each word.
        vector<pair> wordvector;
        copy(begin(counts), end(counts), back_inserter(wordvector));

        sort(begin(wordvector), end(wordvector), [](const pair& x, const pair& y)
        {
            return x.second > y.second;
        });

        size_t size = min(wordvector.size(), count);
        wordvector.erase(begin(wordvector) + size, end(wordvector));

        return wordvector;
    });
}

// Shows the most common words on the UI. 
void MainPage::ShowResults(const vector<pair<wstring, size_t>>& commonWords)
{
    wstringstream ss;
    ss << "The most common words that have five or more letters are:";
    for (auto commonWord : commonWords)
    {
        ss << endl << commonWord.first << L" (" << commonWord.second << L')';
    }

    // Update the UI.
    Results->Text = ref new String(ss.str().c_str());
}

Zmodyfikuj konstruktor, MainPage aby utworzyć łańcuch zadań kontynuacji wyświetlanych w interfejsie użytkownika typowymi słowami w książce Iliad by Homer. Pierwsze dwa zadania kontynuacji, które dzielą tekst na poszczególne wyrazy i znajdują wspólne słowa, mogą być czasochłonne i dlatego są jawnie ustawione do uruchamiania w tle. Końcowe zadanie kontynuacji, które aktualizuje interfejs użytkownika, nie określa kontekstu kontynuacji i dlatego jest zgodne z regułami wątkowania apartamentów.

MainPage::MainPage()
{
    InitializeComponent();

    // To run this example, save the contents of http://www.gutenberg.org/files/6130/6130-0.txt to your Documents folder.
    // Name the file "The Iliad.txt" and save it under UTF-8 encoding.

    // Enable the progress ring.
    Progress->IsActive = true;

    // Find the most common words in the book "The Iliad".

    // Get the file.
    create_task(KnownFolders::DocumentsLibrary->GetFileAsync("The Iliad.txt")).then([](StorageFile^ file)
    {
        // Read the file text.
        return FileIO::ReadTextAsync(file, UnicodeEncoding::Utf8);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](String^ file)
    {
        // Create a word list from the text.
        return MakeWordList(file);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](vector<wstring> words)
    {
        // Find the most common words.
        return FindCommonWords(words, 5, 9);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](vector<pair<wstring, size_t>> commonWords)
    {
        // Stop the progress ring.
        Progress->IsActive = false;

        // Show the results.
        ShowResults(commonWords);

        // We don't specify a continuation context here because we want the continuation 
        // to run on the STA thread.
    });
}

Uwaga

W tym przykładzie pokazano, jak określić konteksty wykonywania i jak utworzyć łańcuch kontynuacji. Pamiętaj, że domyślnie zadanie utworzone na podstawie operacji asynchronicznej uruchamia jego kontynuacje w mieszkaniu o nazwie task::then. W związku z tym w tym przykładzie użyto task_continuation_context::use_arbitrary metody , aby określić, że operacje, które nie obejmują interfejsu użytkownika, mają być wykonywane w wątku w tle.

Poniższa ilustracja przedstawia wyniki CommonWords aplikacji.

Windows Runtime CommonWords app.

W tym przykładzie można obsługiwać anulowanie, ponieważ task obiekty, które obsługują create_async używanie niejawnego tokenu anulowania. Zdefiniuj funkcję roboczą cancellation_token , aby wykonać obiekt, jeśli zadania muszą reagować na anulowanie w sposób współpracy. Aby uzyskać więcej informacji na temat anulowania w PPL, zobacz Anulowanie w PPL

Zobacz też

Środowisko uruchomieniowe współbieżności