Membuat Operasi Asinkron di C++ untuk Aplikasi UWP

Dokumen ini menjelaskan beberapa poin penting yang perlu diingat ketika Anda menggunakan kelas tugas untuk menghasilkan operasi asinkron berbasis Windows ThreadPool di aplikasi Universal Windows Runtime (UWP).

Penggunaan pemrograman asinkron adalah komponen utama dalam model aplikasi Windows Runtime karena memungkinkan aplikasi untuk tetap responsif terhadap input pengguna. Anda dapat memulai tugas yang berjalan lama tanpa memblokir utas UI, dan Anda dapat menerima hasil tugas nanti. Anda juga dapat membatalkan tugas dan menerima pemberitahuan kemajuan saat tugas berjalan di latar belakang. Pemrograman asinkron dokumen di C++ memberikan gambaran umum tentang pola asinkron yang tersedia di Visual C++ untuk membuat aplikasi UWP. Dokumen tersebut mengajarkan cara mengonsumsi dan membuat rantai operasi Windows Runtime asinkron. Bagian ini menjelaskan cara menggunakan jenis dalam ppltasks.h untuk menghasilkan operasi asinkron yang dapat dikonsumsi oleh komponen Windows Runtime lain dan cara mengontrol bagaimana pekerjaan asinkron dijalankan. Pertimbangkan juga untuk membaca pola dan tips pemrograman Asinkron di Hilo (aplikasi Windows Store menggunakan C++ dan XAML) untuk mempelajari bagaimana kami menggunakan kelas tugas untuk menerapkan operasi asinkron di Hilo, aplikasi Windows Runtime menggunakan C++ dan XAML.

Catatan

Anda dapat menggunakan Pustaka Pola Paralel (PPL) dan Pustaka Agen Asinkron di aplikasi UWP. Namun, Anda tidak dapat menggunakan Penjadwal Tugas atau Resource Manager. Dokumen ini menjelaskan fitur tambahan yang disediakan PPL yang hanya tersedia untuk aplikasi UWP, dan bukan ke aplikasi desktop.

Poin-poin penting

  • Gunakan konkurensi::create_async untuk membuat operasi asinkron yang dapat digunakan oleh komponen lain (yang mungkin ditulis dalam bahasa selain C++).

  • Gunakan concurrency::p rogress_reporter untuk melaporkan pemberitahuan kemajuan ke komponen yang memanggil operasi asinkron Anda.

  • Gunakan token pembatalan untuk memungkinkan operasi asinkron internal dibatalkan.

  • Perilaku create_async fungsi tergantung pada jenis pengembalian fungsi kerja yang diteruskan ke fungsi tersebut. Fungsi kerja yang mengembalikan tugas (baik task<T> atau task<void>) berjalan secara sinkron dalam konteks yang disebut create_async. Fungsi kerja yang mengembalikan T atau void berjalan dalam konteks arbitrer.

  • Anda dapat menggunakan metode concurrency::task::then untuk membuat rantai tugas yang berjalan satu demi satu. Dalam aplikasi UWP, konteks default untuk kelanjutan tugas tergantung pada bagaimana tugas itu dibangun. Jika tugas dibuat dengan meneruskan tindakan asinkron ke konstruktor tugas, atau dengan meneruskan ekspresi lambda yang mengembalikan tindakan asinkron, maka konteks default untuk semua kelanjutan tugas tersebut adalah konteks saat ini. Jika tugas tidak dibangun dari tindakan asinkron, maka konteks arbitrer digunakan secara default untuk kelanjutan tugas. Anda dapat mengambil alih konteks default dengan kelas konkurensi::task_continuation_context .

Dalam dokumen ini

Membuat Operasi Asinkron

Anda dapat menggunakan model tugas dan kelanjutan di Pustaka Pola Paralel (PPL) untuk menentukan tugas latar belakang serta tugas tambahan yang berjalan saat tugas sebelumnya selesai. Fungsionalitas ini disediakan oleh kelas konkurensi::tugas . Untuk informasi selengkapnya tentang model ini dan task kelas, lihat Paralelisme Tugas.

Windows Runtime adalah antarmuka pemrograman yang dapat Anda gunakan untuk membuat aplikasi UWP yang hanya berjalan di lingkungan sistem operasi khusus. Aplikasi tersebut menggunakan fungsi resmi, jenis data, dan perangkat, dan didistribusikan dari Microsoft Store. Windows Runtime diwakili oleh Antarmuka Biner Aplikasi (ABI). ABI adalah kontrak biner yang mendasarinya yang membuat API Runtime Windows tersedia untuk bahasa pemrograman seperti Visual C++.

Dengan menggunakan Windows Runtime, Anda dapat menggunakan fitur terbaik dari berbagai bahasa pemrograman dan menggabungkannya menjadi satu aplikasi. Misalnya, Anda dapat membuat UI di JavaScript dan melakukan logika aplikasi intensif komputasi dalam komponen C++. Kemampuan untuk melakukan operasi intensif komputasi ini di latar belakang adalah faktor kunci dalam menjaga UI Anda tetap responsif. task Karena kelas ini khusus untuk C++, Anda harus menggunakan antarmuka Windows Runtime untuk mengomunikasikan operasi asinkron ke komponen lain (yang mungkin ditulis dalam bahasa selain C++). Windows Runtime menyediakan empat antarmuka yang dapat Anda gunakan untuk mewakili operasi asinkron:

Windows::Foundation::IAsyncAction
Mewakili tindakan asinkron.

Windows::Foundation::IAsyncActionWithProgress<TProgress>
Mewakili tindakan asinkron yang melaporkan kemajuan.

Windows::Foundation::IAsyncOperation<TResult>
Mewakili operasi asinkron yang mengembalikan hasil.

Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Mewakili operasi asinkron yang mengembalikan hasil dan melaporkan kemajuan.

Gagasan tindakan berarti bahwa tugas asinkron tidak menghasilkan nilai (pikirkan fungsi yang mengembalikan void). Gagasan operasi berarti bahwa tugas asinkron memang menghasilkan nilai. Gagasan kemajuan berarti bahwa tugas dapat melaporkan pesan kemajuan ke pemanggil. JavaScript, .NET Framework, dan Visual C++ masing-masing menyediakan caranya sendiri untuk membuat instans antarmuka ini untuk digunakan di seluruh batas ABI. Untuk Visual C++, PPL menyediakan fungsi konkurensi::create_async . Fungsi ini membuat tindakan atau operasi asinkron Windows Runtime yang mewakili penyelesaian tugas. Fungsi ini create_async mengambil fungsi kerja (biasanya ekspresi lambda), secara internal membuat task objek, dan membungkus tugas tersebut di salah satu dari empat antarmuka Windows Runtime asinkron.

Catatan

Gunakan create_async hanya ketika Anda harus membuat fungsionalitas yang dapat diakses dari bahasa lain atau komponen Windows Runtime lainnya. Gunakan kelas secara task langsung ketika Anda tahu bahwa operasi diproduksi dan dikonsumsi oleh kode C++ dalam komponen yang sama.

Jenis create_async pengembalian ditentukan oleh jenis argumennya. Misalnya, jika fungsi kerja Anda tidak mengembalikan nilai dan tidak melaporkan kemajuan, create_async mengembalikan IAsyncAction. Jika fungsi kerja Anda tidak mengembalikan nilai dan juga melaporkan kemajuan, create_async mengembalikan IAsyncActionWithProgress. Untuk melaporkan kemajuan, berikan objek concurrency::p rogress_reporter sebagai parameter untuk fungsi kerja Anda. Kemampuan untuk melaporkan kemajuan memungkinkan Anda melaporkan jumlah pekerjaan yang dilakukan dan jumlah berapa yang masih ada (misalnya, sebagai persentase). Ini juga memungkinkan Anda untuk melaporkan hasil saat tersedia.

Antarmuka IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult>, dan IAsyncActionOperationWithProgress<TProgress, TProgress> masing-masing menyediakan Cancel metode yang memungkinkan Anda membatalkan operasi asinkron. Kelas task berfungsi dengan token pembatalan. Saat Anda menggunakan token pembatalan untuk membatalkan pekerjaan, runtime tidak memulai pekerjaan baru yang berlangganan token tersebut. Pekerjaan yang sudah aktif dapat memantau token pembatalannya dan berhenti ketika bisa. Mekanisme ini dijelaskan secara lebih rinci dalam dokumen Pembatalan di PPL. Anda dapat menyambungkan pembatalan tugas dengan metode Windows Runtime Cancel dengan dua cara. Pertama, Anda dapat menentukan fungsi kerja yang Anda teruskan untuk create_async mengambil objek konkurensi::cancellation_token . Cancel Ketika metode dipanggil, token pembatalan ini dibatalkan dan aturan pembatalan normal berlaku untuk objek yang mendasar taskcreate_async yang mendukung panggilan. Jika Anda tidak menyediakan cancellation_token objek, objek yang mendasar task mendefinisikan objek secara implisit. cancellation_token Tentukan objek saat Anda perlu merespons pembatalan secara kooperatif dalam fungsi kerja Anda. Contoh bagian: Mengontrol Eksekusi di Aplikasi Runtime Windows dengan C++ dan XAML menunjukkan contoh cara melakukan pembatalan di aplikasi Platform Windows Universal (UWP) dengan C# dan XAML yang menggunakan komponen Windows Runtime C++ kustom.

Peringatan

Dalam rantai kelanjutan tugas, selalu bersihkan status lalu panggil konkurensi::cancel_current_task ketika token pembatalan dibatalkan. Jika Anda kembali lebih awal alih-alih memanggil cancel_current_task, operasi beralih ke status selesai alih-alih status dibatalkan.

Tabel berikut ini meringkas kombinasi yang dapat Anda gunakan untuk menentukan operasi asinkron di aplikasi Anda.

Untuk membuat antarmuka Windows Runtime ini Kembalikan tipe ini dari create_async Teruskan jenis parameter ini ke fungsi kerja Anda untuk menggunakan token pembatalan implisit Teruskan jenis parameter ini ke fungsi kerja Anda untuk menggunakan token pembatalan eksplisit
IAsyncAction void atau task<void> (tidak ada) (cancellation_token)
IAsyncActionWithProgress<TProgress> void atau task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T atau task<T> (tidak ada) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T atau task<T> (progress_reporter) (progress_reporter, cancellation_token)

Anda dapat mengembalikan nilai atau task objek dari fungsi kerja yang Anda teruskan ke create_async fungsi . Variasi ini menghasilkan perilaku yang berbeda. Saat Anda mengembalikan nilai, fungsi kerja dibungkus dalam task sehingga dapat dijalankan pada utas latar belakang. Selain itu, yang mendasar task menggunakan token pembatalan implisit. Sebaliknya, jika Anda mengembalikan task objek, fungsi kerja berjalan secara sinkron. Oleh karena itu, jika Anda mengembalikan task objek, pastikan bahwa operasi panjang apa pun dalam fungsi kerja Anda juga berjalan sebagai tugas untuk memungkinkan aplikasi Anda tetap responsif. Selain itu, yang mendasar task tidak menggunakan token pembatalan implisit. Oleh karena itu, Anda perlu menentukan fungsi kerja Anda untuk mengambil cancellation_token objek jika Anda memerlukan dukungan untuk pembatalan saat Anda mengembalikan task objek dari create_async.

Contoh berikut menunjukkan berbagai cara untuk membuat IAsyncAction objek yang dapat dikonsumsi oleh komponen Windows Runtime lainnya.

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

Contoh: Membuat Komponen Runtime C++ Windows dan Mengonsumsinya dari C#

Pertimbangkan aplikasi yang menggunakan XAML dan C# untuk menentukan UI dan komponen C++ Windows Runtime untuk melakukan operasi intensif komputasi. Dalam contoh ini, komponen C++ menghitung angka mana dalam rentang tertentu yang prima. Untuk mengilustrasikan perbedaan di antara empat antarmuka tugas asinkron Windows Runtime, mulailah, di Visual Studio, dengan membuat Solusi Kosong dan menmamakannya Primes. Kemudian tambahkan ke solusi proyek Komponen Runtime Windows dan beri nama PrimesLibrary. Tambahkan kode berikut ke file header C++ yang dihasilkan (contoh ini mengganti nama Class1.h menjadi Primes.h). Setiap public metode mendefinisikan salah satu dari empat antarmuka asinkron. Metode yang mengembalikan nilai mengembalikan objek int> Windows::Foundation::Collections::IVector<. Metode yang melaporkan kemajuan menghasilkan double nilai yang menentukan persentase pekerjaan keseluruhan yang telah selesai.

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

Catatan

Menurut konvensi, nama metode asinkron di Windows Runtime biasanya diakhapi dengan "Asinkron".

Tambahkan kode berikut ke file sumber C++ yang dihasilkan (contoh ini mengganti nama Class1.cpp menjadi Primes.cpp). Fungsi menentukan is_prime apakah inputnya prima. Metode yang tersisa mengimplementasikan Primes kelas . Setiap panggilan untuk create_async menggunakan tanda tangan yang kompatibel dengan metode tempat panggilan dipanggil. Misalnya, karena Primes::ComputePrimesAsync mengembalikan IAsyncAction, fungsi kerja yang disediakan untuk create_async tidak mengembalikan nilai dan tidak mengambil progress_reporter objek sebagai parameternya.

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

Setiap metode terlebih dahulu melakukan validasi untuk memastikan bahwa parameter input tidak negatif. Jika nilai input negatif, metode melempar Platform::InvalidArgumentException. Penanganan kesalahan dijelaskan nanti di bagian ini.

Untuk menggunakan metode ini dari aplikasi UWP, gunakan templat Aplikasi Kosong Visual C# (XAML) untuk menambahkan proyek kedua ke solusi Visual Studio. Contoh ini menamai proyek Primes. Kemudian, dari Primes proyek, tambahkan referensi ke PrimesLibrary proyek.

Tambahkan kode berikut ke MainPage.xaml. Kode ini menentukan UI sehingga Anda dapat memanggil komponen C++ dan menampilkan hasil.

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

Tambahkan kode berikut ke MainPage kelas di MainPage.xaml. Kode ini mendefinisikan Primes objek dan penanganan aktivitas tombol.

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

Metode ini menggunakan async kata kunci dan await untuk memperbarui UI setelah operasi asinkron selesai. Untuk informasi tentang pengodean asinkron di aplikasi UWP, lihat Pemrograman utas dan asinkron.

Metode getPrimesCancellation dan cancelGetPrimes bekerja sama untuk memungkinkan pengguna membatalkan operasi. Ketika pengguna memilih tombol Batalkan, metode memanggil IAsyncOperationWithProgress<TResult, TProgress>::Cancel untuk membatalkan operasi.cancelGetPrimes Runtime Konkurensi, yang mengelola operasi asinkron yang mendasarinya, melemparkan jenis pengecualian internal yang ditangkap oleh Windows Runtime untuk mengomunikasikan bahwa pembatalan telah selesai. Untuk informasi selengkapnya tentang model pembatalan, lihat Pembatalan.

Penting

Untuk mengaktifkan PPL melaporkan dengan benar ke Windows Runtime bahwa ia telah membatalkan operasi, jangan menangkap jenis pengecualian internal ini. Ini berarti bahwa Anda juga tidak boleh menangkap semua pengecualian (catch (...)). Jika Anda harus menangkap semua pengecualian, ulangi pengecualian untuk memastikan bahwa Windows Runtime dapat menyelesaikan operasi pembatalan.

Ilustrasi berikut menunjukkan Primes aplikasi setelah setiap opsi dipilih.

Windows Runtime Primes app.

Untuk contoh yang menggunakan create_async untuk membuat tugas asinkron yang dapat digunakan oleh bahasa lain, lihat Menggunakan C++ dalam sampel Bing Peta Trip Optimizer.

Mengontrol Utas Eksekusi

Windows Runtime menggunakan model utas COM. Dalam model ini, objek dihosting di apartemen yang berbeda, tergantung pada bagaimana mereka menangani sinkronisasi mereka. Objek thread-safe dihosting di apartemen multi-utas (MTA). Objek yang harus diakses oleh satu utas dihosting di apartemen berulir tunggal (STA).

Dalam aplikasi yang memiliki UI, utas ASTA (Application STA) bertanggung jawab untuk memompa pesan jendela dan merupakan satu-satunya utas dalam proses yang dapat memperbarui kontrol UI yang dihosting STA. Ini memiliki dua konsekuensi. Pertama, untuk mengaktifkan aplikasi agar tetap responsif, semua operasi intensif CPU dan I/O tidak boleh dijalankan pada utas ASTA. Kedua, hasil yang berasal dari utas latar belakang harus dinamai kembali ke ASTA untuk memperbarui UI. Di aplikasi C++ UWP, MainPage dan halaman XAML lainnya semuanya berjalan di ATSA. Oleh karena itu, kelanjutan tugas yang dinyatakan pada ASTA dijalankan di sana secara default sehingga Anda dapat memperbarui kontrol langsung dalam isi kelanjutan. Namun, jika Anda menumpuk tugas di tugas lain, setiap kelanjutan pada tugas berlapis tersebut berjalan di MTA. Oleh karena itu, Anda perlu mempertimbangkan apakah akan secara eksplisit menentukan konteks apa yang dijalankan kelanjutan ini.

Tugas yang dibuat dari operasi asinkron, seperti IAsyncOperation<TResult>, menggunakan semantik khusus yang dapat membantu Anda mengabaikan detail utas. Meskipun operasi mungkin berjalan pada utas latar belakang (atau mungkin tidak didukung oleh utas sama sekali), kelanjutannya secara default dijamin berjalan di apartemen yang memulai operasi kelanjutan (dengan kata lain, dari apartemen yang disebut task::then). Anda dapat menggunakan kelas konkurensi::task_continuation_context untuk mengontrol konteks eksekusi kelanjutan. Gunakan metode pembantu statis ini untuk membuat task_continuation_context objek:

Anda dapat meneruskan task_continuation_context objek ke tugas::lalu metode untuk secara eksplisit mengontrol konteks eksekusi kelanjutan atau Anda dapat meneruskan tugas ke apartemen lain dan kemudian memanggil task::then metode untuk secara implisit mengontrol konteks eksekusi.

Penting

Karena utas UI utama aplikasi UWP berjalan di bawah STA, kelanjutan yang Anda buat di STA tersebut secara default berjalan di STA. Oleh karena itu, kelanjutan yang Anda buat pada MTA berjalan pada MTA.

Bagian berikut menunjukkan aplikasi yang membaca file dari disk, menemukan kata yang paling umum dalam file tersebut, lalu menampilkan hasilnya di UI. Operasi akhir, memperbarui UI, terjadi pada utas UI.

Penting

Perilaku ini khusus untuk aplikasi UWP. Untuk aplikasi desktop, Anda tidak mengontrol tempat kelanjutan berjalan. Sebagai gantinya, penjadwal memilih utas pekerja untuk menjalankan setiap kelanjutan.

Penting

Jangan panggil konkurensi::task::wait di isi kelanjutan yang berjalan di STA. Jika tidak, runtime melemparkan konkurensi::invalid_operation karena metode ini memblokir utas saat ini dan dapat menyebabkan aplikasi menjadi tidak responsif. Namun, Anda dapat memanggil metode concurrency::task::get untuk menerima hasil tugas antecedent dalam kelanjutan berbasis tugas.

Contoh: Mengontrol Eksekusi di Aplikasi Runtime Windows dengan C++ dan XAML

Pertimbangkan aplikasi C++ XAML yang membaca file dari disk, menemukan kata-kata yang paling umum dalam file tersebut, lalu menunjukkan hasilnya di UI. Untuk membuat aplikasi ini, mulailah, di Visual Studio, dengan membuat proyek Aplikasi Kosong (Universal Windows) dan menmamakannya CommonWords. Dalam manifes aplikasi Anda, tentukan kemampuan Pustaka Dokumen untuk mengaktifkan aplikasi untuk mengakses folder Dokumen. Tambahkan juga jenis file Teks (.txt) ke bagian deklarasi manifes aplikasi. Untuk informasi selengkapnya tentang kemampuan dan deklarasi aplikasi, lihat Pengemasan, penyebaran, dan kueri aplikasi Windows.

Grid Perbarui elemen di MainPage.xaml untuk menyertakan ProgressRing elemen dan TextBlock elemen. ProgressRing menunjukkan bahwa operasi sedang berlangsung dan TextBlock menunjukkan hasil komputasi.

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

Tambahkan pernyataan berikut #include ke pch.h.

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

Tambahkan deklarasi metode berikut ke MainPage kelas (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);

Tambahkan pernyataan berikut using ke MainPage.cpp.

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

Di MainPage.cpp, terapkan MainPage::MakeWordListmetode , MainPage::FindCommonWords, dan MainPage::ShowResults . dan MainPage::MakeWordListMainPage::FindCommonWords melakukan operasi intensif komputasi. Metode MainPage::ShowResults ini menampilkan hasil komputasi di UI.

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

MainPage Ubah konstruktor untuk membuat rantai tugas kelanjutan yang ditampilkan di UI kata-kata umum dalam buku The Iliad by Homer. Dua tugas kelanjutan pertama, yang membagi teks menjadi kata-kata individual dan menemukan kata-kata umum, dapat memakan waktu dan oleh karena itu secara eksplisit diatur untuk berjalan di latar belakang. Tugas kelanjutan akhir, yang memperbarui UI, tidak menentukan konteks kelanjutan, dan oleh karena itu mengikuti aturan utas apartemen.

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

Catatan

Contoh ini menunjukkan cara menentukan konteks eksekusi dan cara menyusun rantai kelanjutan. Ingat bahwa secara default tugas yang dibuat dari operasi asinkron menjalankan kelanjutannya di apartemen yang disebut task::then. Oleh karena itu, contoh ini menggunakan task_continuation_context::use_arbitrary untuk menentukan bahwa operasi yang tidak melibatkan UI dilakukan pada utas latar belakang.

Ilustrasi berikut menunjukkan hasil CommonWords aplikasi.

Windows Runtime CommonWords app.

Dalam contoh ini, dimungkinkan untuk mendukung pembatalan karena task objek yang mendukung create_async menggunakan token pembatalan implisit. Tentukan fungsi kerja Anda untuk mengambil cancellation_token objek jika tugas Anda perlu merespons pembatalan dengan cara yang kooperatif. Untuk informasi selengkapnya tentang pembatalan di PPL, lihat Pembatalan di PPL

Baca juga

Runtime Konkurensi