Görev Parallelliği (Eşzamanlılık Çalışma Zamanı)

Eşzamanlılık Çalışma Zamanı'nda görev, belirli bir işi gerçekleştiren ve genellikle diğer görevlerle paralel olarak çalışan bir çalışma birimidir. Bir görev, bir görev grubu halinde düzenlenmiş ek, daha ayrıntılı görevler halinde ayrıştırılabilir.

Zaman uyumsuz kod yazarken görevleri kullanırsınız ve zaman uyumsuz işlem tamamlandıktan sonra bazı işlemlerin gerçekleşmesini istersiniz. Örneğin, bir dosyadan zaman uyumsuz olarak okumak için bir görev kullanabilir ve sonra verileri kullanılabilir duruma geldikten sonra işlemek için başka bir görev (bu belgenin ilerleyen bölümlerinde açıklanan bir devamlılık görevi) kullanabilirsiniz. Buna karşılık, paralel çalışmayı daha küçük parçalara ayırmak için görev gruplarını kullanabilirsiniz. Örneğin, kalan işi iki bölüme ayıran özyinelemeli bir algoritmanız olduğunu varsayalım. Görev gruplarını kullanarak bu bölümleri eşzamanlı olarak çalıştırabilir ve sonra bölünmüş çalışmanın tamamlanmasını bekleyebilirsiniz.

İpucu

Bir koleksiyonun her öğesine paralel olarak aynı yordamı uygulamak istediğinizde, bir görev veya görev grubu yerine eşzamanlılık::p arallel_for gibi paralel bir algoritma kullanın. Paralel algoritmalar hakkında daha fazla bilgi için bkz . Paralel Algoritmalar.

Önemli Noktalar

  • Başvuruya göre bir lambda ifadesine değişken geçirdiğinizde, görev bitene kadar bu değişkenin ömrünün devam ettiğini garanti etmeniz gerekir.

  • Zaman uyumsuz kod yazarken görevleri ( eşzamanlılık::görev sınıfı) kullanın. Görev sınıfı, Eşzamanlılık Çalışma Zamanı'nı değil zamanlayıcı olarak Windows ThreadPool'u kullanır.

  • Paralel çalışmayı daha küçük parçalara ayırmak ve sonra bu küçük parçaların tamamlanmasını beklemek istediğinizde görev gruplarını (eşzamanlılık::task_group sınıfı veya concurrency::p arallel_invoke algoritması) kullanın.

  • Devamlılık oluşturmak için concurrency::task::then yöntemini kullanın. Devamlılık, başka bir görev tamamlandıktan sonra zaman uyumsuz olarak çalışan bir görevdir. Zaman uyumsuz bir iş zinciri oluşturmak için istediğiniz sayıda devamlılık bağlayabilirsiniz.

  • Öncül görev iptal edildiğinde veya özel durum oluştursa bile, öncül görev tamamlandığında, görev tabanlı devamlılık her zaman yürütme için zamanlanır.

  • Bir görev kümesinin her üyesi tamamlandıktan sonra tamamlayan bir görev oluşturmak için eşzamanlılık::when_all kullanın. Bir görev kümesinin bir üyesi tamamlandıktan sonra tamamlanan bir görev oluşturmak için eşzamanlılık::when_any kullanın.

  • Görevler ve görev grupları Paralel Desen kitaplığı (PPL) iptal mekanizmasına katılabilir. Daha fazla bilgi için bkz . PPL'de İptal.

  • Çalışma zamanının görevler ve görev grupları tarafından oluşan özel durumları nasıl işlediğini öğrenmek için bkz . Özel Durum İşleme.

Bu Belgede

Lambda İfadeleri kullanma

Kısa söz dizimi nedeniyle lambda ifadeleri, görevler ve görev grupları tarafından gerçekleştirilen çalışmayı tanımlamanın yaygın bir yoludur. İşte bazı kullanım ipuçları:

  • Görevler genellikle arka plan iş parçacıklarında çalıştığından, lambda ifadelerindeki değişkenleri yakaladığınızda nesne ömrüne dikkat edin. Bir değişkeni değere göre yakaladığınızda, lambda gövdesinde bu değişkenin bir kopyası oluşturulur. Başvuruya göre yakaladığınızda, bir kopya yapılmaz. Bu nedenle, başvuruyla yakaladığınız herhangi bir değişkenin kullanım ömrünün onu kullanan görevden uzun olduğundan emin olun.

  • Bir göreve lambda ifadesi geçirdiğinizde, yığında başvuruya göre ayrılan değişkenleri yakalamayın.

  • Lambda ifadelerinde yakaladığınız değişkenler hakkında açık olun, böylece neyi yakaladığınızı değere göre ve başvuruya göre tanımlayabilirsiniz. Bu nedenle lambda ifadeleri için veya [&] seçeneklerini kullanmamanızı [=] öneririz.

Bir devamlılık zincirindeki bir görevin bir değişkene atanması ve başka bir görevin bu değişkeni okuması yaygın bir desendir. Her devamlılık görevi değişkenin farklı bir kopyasını barındıracağından değere göre yakalayamazsınız. Yığına ayrılmış değişkenler için, değişkenin artık geçerli olmayabileceği için başvuruya göre yakalayamazsınız.

Bu sorunu çözmek için std::shared_ptr gibi bir akıllı işaretçi kullanarak değişkeni sarmalayıp akıllı işaretçiyi değere göre geçirin. Bu şekilde, temel alınan nesneye atanabilir ve nesnesinden okunabilir ve bunu kullanan görevlerden daha uzun sürebilir. Değişken bir Windows Çalışma Zamanı nesnesine işaretçi veya başvuru sayılan tanıtıcı (^) olduğunda bile bu tekniği kullanın. Basit bir örnek verelim:

// lambda-task-lifetime.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <string>

using namespace concurrency;
using namespace std;

task<wstring> write_to_string()
{
    // Create a shared pointer to a string that is 
    // assigned to and read by multiple tasks.
    // By using a shared pointer, the string outlives
    // the tasks, which can run in the background after
    // this function exits.
    auto s = make_shared<wstring>(L"Value 1");

    return create_task([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value.
        *s = L"Value 2";

    }).then([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value and return the string.
        *s = L"Value 3";
        return *s;
    });
}

int wmain()
{
    // Create a chain of tasks that work with a string.
    auto t = write_to_string();

    // Wait for the tasks to finish and print the result.
    wcout << L"Final value: " << t.get() << endl;
}

/* Output:
    Current value: Value 1
    Current value: Value 2
    Final value: Value 3
*/

Lambda ifadeleri hakkında daha fazla bilgi için bkz . Lambda İfadeleri.

Görev Sınıfı

Bağımlı işlemler kümesinde görev oluşturmak için eşzamanlılık::task sınıfını kullanabilirsiniz. Bu bileşim modeli, devamlılıklar gösterimi tarafından desteklenir. Devam, önceki veya öncül görev tamamlandığında kodun yürütülmesini sağlar. Öncül görevin sonucu, bir veya daha fazla devamlılık görevine giriş olarak geçirilir. Bir öncül görev tamamlandığında, bekleyen tüm devamlılık görevleri yürütme için zamanlanır. Her devamlılık görevi, öncül görevin sonucunun bir kopyasını alır. Buna karşılık, bu devamlılık görevleri de diğer devamlılıklar için öncül görevler olabilir, böylece bir görev zinciri oluşturabilirsiniz. Devamlılıklar, aralarında belirli bağımlılıklara sahip rastgele uzunluklu görev zincirleri oluşturmanıza yardımcı olur. Ayrıca bir görev, bir görev başlamadan önce veya çalışırken işbirliğine dayalı bir şekilde iptale katılabilir. Bu iptal modeli hakkında daha fazla bilgi için bkz . PPL'de İptal.

task bir şablon sınıfıdır. type parametresi T , görev tarafından üretilen sonucun türüdür. Bu tür, görev bir değer döndürmezse olabilir void . T değiştiriciyi const kullanamaz.

Bir görev oluşturduğunuzda, görev gövdesini gerçekleştiren bir çalışma işlevi sağlarsınız. Bu iş işlevi bir lambda işlevi, işlev işaretçisi veya işlev nesnesi biçiminde gelir. Sonucu almadan görevin tamamlanmasını beklemek için eşzamanlılık::task::wait yöntemini çağırın. yöntemi, task::wait görevin tamamlanıp tamamlanmadığını veya iptal edilip edilmediğini açıklayan bir eşzamanlılık::task_status değeri döndürür. Görevin sonucunu almak için eşzamanlılık::task::get yöntemini çağırın. Bu yöntem görevin bitmesini beklemeye çağırır task::wait ve bu nedenle sonuç kullanılabilir olana kadar geçerli iş parçacığının yürütülmesini engeller.

Aşağıdaki örnekte bir görevin nasıl oluşturulacağı, sonucunun nasıl bekleneceği ve değerinin nasıl görüntüleneceği gösterilmektedir. Bu belgedeki örneklerde lambda işlevleri kullanılır çünkü bunlar daha kısa bir söz dizimi sağlar. Ancak, görevleri kullanırken işlev işaretçilerini ve işlev nesnelerini de kullanabilirsiniz.

// basic-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Create a task.
    task<int> t([]()
    {
        return 42;
    });

    // In this example, you don't necessarily need to call wait() because
    // the call to get() also waits for the result.
    t.wait();

    // Print the result.
    wcout << t.get() << endl;
}

/* Output:
    42
*/

concurrency::create_task işlevini kullandığınızda, türünü bildirmek yerine anahtar sözcüğünü kullanabilirsinizauto. Örneğin, kimlik matrisini oluşturan ve yazdıran şu kodu göz önünde bulundurun:

// create-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <string>
#include <iostream>
#include <array>

using namespace concurrency;
using namespace std;

int wmain()
{
    task<array<array<int, 10>, 10>> create_identity_matrix([]
    {
        array<array<int, 10>, 10> matrix;
        int row = 0;
        for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
        {
            fill(begin(matrixRow), end(matrixRow), 0);
            matrixRow[row] = 1;
            row++;
        });
        return matrix;
    });

    auto print_matrix = create_identity_matrix.then([](array<array<int, 10>, 10> matrix)
    {
        for_each(begin(matrix), end(matrix), [](array<int, 10>& matrixRow) 
        {
            wstring comma;
            for_each(begin(matrixRow), end(matrixRow), [&comma](int n) 
            {
                wcout << comma << n;
                comma = L", ";
            });
            wcout << endl;
        });
    });

    print_matrix.wait();
}
/* Output:
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 1, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 1, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 1, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 1, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 1, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 1, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1
*/

eşdeğer işlemi create_task oluşturmak için işlevini kullanabilirsiniz.

auto create_identity_matrix = create_task([]
{
    array<array<int, 10>, 10> matrix;
    int row = 0;
    for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
    {
        fill(begin(matrixRow), end(matrixRow), 0);
        matrixRow[row] = 1;
        row++;
    });
    return matrix;
});

Bir görevin yürütülmesi sırasında bir özel durum oluşturulursa, çalışma zamanı bu özel durumu sonraki çağrıda task::get veya veya task::waitgörev tabanlı bir devamlılıkta sıralar. Görev özel durum işleme mekanizması hakkında daha fazla bilgi için bkz . Özel Durum İşleme.

kullanan bir örnek taskiçin eşzamanlılık::task_completion_event, iptal, bkz. İzlenecek yol: Görevleri ve XML HTTP İsteklerini Kullanarak Bağlanma. (Sınıf task_completion_event , bu belgenin ilerleyen bölümlerinde açıklanmıştır.)

İpucu

UWP uygulamalarındaki görevlere özgü ayrıntıları öğrenmek için bkz . C++ dilinde zaman uyumsuz programlama ve UWP Uygulamaları için C++'ta Zaman Uyumsuz İşlemler Oluşturma.

Devam Görevleri

Zaman uyumsuz programlamada, bir zaman uyumsuz işlemin tamamlanmasının ardından ikinci bir işlemi çağırması ve ona veri geçirmesi çok yaygındır. Geleneksel olarak, bu geri çağırma yöntemleri kullanılarak yapılır. Eşzamanlılık Çalışma Zamanı'nda, aynı işlevsellik devamlılık görevleri tarafından sağlanır. Devamlılık görevi (aynı zamanda devamlılık olarak da bilinir), öncül tamamlandığında öncül olarak bilinen başka bir görev tarafından çağrılan zaman uyumsuz bir görevdir. Devamlılıkları kullanarak şunları yapabilirsiniz:

  • Öncülden devama veri geçirme.

  • Devamın çağrıldığı veya çağrılmayacak olduğu kesin koşulları belirtin.

  • Devamı başlamadan önce veya çalışırken işbirliğiyle iptal edin.

  • Devamın nasıl zamanlanması gerektiği hakkında ipuçları sağlayın. (Bu yalnızca Evrensel Windows Platformu (UWP) uygulamaları için geçerlidir. Daha fazla bilgi için bkz . UWP Uygulamaları için C++'ta Zaman Uyumsuz İşlemler Oluşturma.)

  • Aynı öncülden birden çok devamlılık çağırın.

  • Birden çok öncülden tümü veya herhangi biri tamamlandığında bir devamlılık çağırın.

  • Zincir devamı, herhangi bir uzunlukta birbiri ardına devam eder.

  • Öncül tarafından oluşan özel durumları işlemek için bir devamlılık kullanın.

Bu özellikler, ilk görev tamamlandığında bir veya daha fazla görevi yürütmenizi sağlar. Örneğin, ilk görev dosyayı diskten okuduktan sonra sıkıştıran bir devamlılık oluşturabilirsiniz.

Aşağıdaki örnek, bir öncekini eşzamanlılığı kullanacak şekilde değiştirir::task::then yöntemi, kullanılabilir olduğunda öncül görevin değerini yazdıran bir devamlılık zamanlamak için kullanılır.

// basic-continuation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    {
        return 42;
    });

    t.then([](int result)
    {
        wcout << result << endl;
    }).wait();

    // Alternatively, you can chain the tasks directly and
    // eliminate the local variable.
    /*create_task([]() -> int
    {
        return 42;
    }).then([](int result)
    {
        wcout << result << endl;
    }).wait();*/
}

/* Output:
    42
*/

Görevleri istediğiniz uzunlukta zincirleyebilir ve iç içe yerleştirebilirsiniz. Bir görevin birden çok devamı da olabilir. Aşağıdaki örnekte, önceki görevin değerini üç kez artıran temel bir devamlılık zinciri gösterilmektedir.

// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    { 
        return 0;
    });
    
    // Create a lambda that increments its input value.
    auto increment = [](int n) { return n + 1; };

    // Run a chain of continuations and print the result.
    int result = t.then(increment).then(increment).then(increment).get();
    wcout << result << endl;
}

/* Output:
    3
*/

Bir devamlılık başka bir görev de döndürebilir. İptal yoksa, bu görev sonraki devamlılık öncesinde yürütülür. Bu teknik, zaman uyumsuz unwrapping olarak bilinir. Arka planda ek çalışma yapmak istediğinizde ancak geçerli görevin geçerli iş parçacığını engellemesini istemediğinizde zaman uyumsuz unwrapping yararlı olur. (Bu, UWP uygulamalarında yaygındır ve burada devamlılıklar ui iş parçacığında çalıştırılabilir). Aşağıdaki örnekte üç görev gösterilmektedir. İlk görev, bir devamlılık görevinden önce çalıştırılacak başka bir görev döndürür.

// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]()
    {
        wcout << L"Task A" << endl;

        // Create an inner task that runs before any continuation
        // of the outer task.
        return create_task([]()
        {
            wcout << L"Task B" << endl;
        });
    });
  
    // Run and wait for a continuation of the outer task.
    t.then([]()
    {
        wcout << L"Task C" << endl;
    }).wait();
}

/* Output:
    Task A
    Task B
    Task C
*/

Önemli

Görevin devamı türünde bir iç içe görev Ndöndürdüğünde, sonuçta elde edilen görev türüne sahip , Ndeğil task<N>ve iç içe görev tamamlandığında tamamlanır. Başka bir deyişle, devamlılık iç içe görev açma işlemini gerçekleştirir.

Değer Tabanlı ve Görev Tabanlı Devamlılıklar Karşılaştırması

Dönüş türü olan bir task nesne verildiğinde T, türü T task<T> veya devamlılık görevlerine bir değer sağlayabilirsiniz. Tür T alan bir devamlılık, değer tabanlı devamlılık olarak bilinir. Değer tabanlı devamlılık, öncül görev hatasız tamamlandığında ve iptal edilmediğinde yürütülmeye zamanlanır. Türü task<T> parametresi olarak alan bir devamlılık, görev tabanlı devamlılık olarak bilinir. Öncül görev iptal edildiğinde veya özel durum oluştursa bile, öncül görev tamamlandığında, görev tabanlı devamlılık her zaman yürütme için zamanlanır. Ardından, öncül görevin sonucunu almak için öğesini çağırabilirsiniz task::get . Öncül görev iptal edildiyse eşzamanlılıktask::get::task_canceled oluşturur. Öncül görev özel durum oluşturduysa, task::get bu özel durumu yeniden oluşturur. Görev tabanlı devamlılık, öncül görevi iptal edildiğinde iptal edildi olarak işaretlenmez.

Görev Oluşturma

Bu bölümde, ortak desenleri uygulamak için birden çok görev oluşturmanıza yardımcı olabilecek eşzamanlılık::when_all ve eşzamanlılık::when_any işlevleri açıklanmaktadır.

when_all İşlevi

İşlev, when_all bir dizi görev tamamlandıktan sonra tamamlanan bir görev oluşturur. Bu işlev, kümedeki her görevin sonucunu içeren bir std::vector nesnesi döndürür. Aşağıdaki temel örnek, diğer üç görevin tamamlanmasını temsil eden bir görev oluşturmak için kullanır when_all .

// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<void>, 3> tasks = 
    {
        create_task([] { wcout << L"Hello from taskA." << endl; }),
        create_task([] { wcout << L"Hello from taskB." << endl; }),
        create_task([] { wcout << L"Hello from taskC." << endl; })
    };

    auto joinTask = when_all(begin(tasks), end(tasks));

    // Print a message from the joining thread.
    wcout << L"Hello from the joining thread." << endl;

    // Wait for the tasks to finish.
    joinTask.wait();
}

/* Sample output:
    Hello from the joining thread.
    Hello from taskA.
    Hello from taskC.
    Hello from taskB.
*/

Not

Geçirdiğiniz when_all görevler tekdüzen olmalıdır. Başka bir deyişle, tümünün aynı tür döndürmesi gerekir.

Aşağıdaki örnekte gösterildiği gibi, bir dizi görev tamamlandıktan sonra tamamlanan bir görev oluşturmak için söz dizimini de kullanabilirsiniz && .

auto t = t1 && t2; // same as when_all

Bir dizi görev tamamlandıktan sonra bir eylem gerçekleştirmek için birlikte when_all bir devamlılık kullanmak yaygın bir durumdur. Aşağıdaki örnek, her biri bir int sonuç oluşturan üç görev toplamını yazdırmak için öncekini değiştirir.

// Start multiple tasks.
array<task<int>, 3> tasks =
{
    create_task([]() -> int { return 88; }),
    create_task([]() -> int { return 42; }),
    create_task([]() -> int { return 99; })
};

auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{
    wcout << L"The sum is " 
          << accumulate(begin(results), end(results), 0)
          << L'.' << endl;
});

// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;

// Wait for the tasks to finish.
joinTask.wait();

/* Output:
    Hello from the joining thread.
    The sum is 229.
*/

Bu örnekte, görev tabanlı bir devamlılık oluşturmayı da belirtebilirsiniz task<vector<int>> .

Bir görev kümesindeki herhangi bir görev iptal edilirse veya bir özel durum oluşturursa, when_all hemen tamamlanır ve kalan görevlerin bitmesini beklemez. Özel durum oluşursa, veya döndüren görev nesnesini when_all çağırdığınızda task::get task::wait çalışma zamanı özel durumu yeniden oluşturur. Birden fazla görev atarsa, çalışma zamanı bunlardan birini seçer. Bu nedenle, tüm görevler tamamlandıktan sonra tüm özel durumları gözlemlediğinizden emin olun; işlenmeyen bir görev özel durumu uygulamanın sonlandırılmasına neden olur.

Programınızın tüm özel durumları gözlemlediğinden emin olmak için kullanabileceğiniz bir yardımcı program işlevi aşağıdadır. Sağlanan aralıktaki her görev için, observe_all_exceptions yeniden fırlatılma durumunda oluşan özel durumları tetikler ve ardından bu özel durumu yutar.

// Observes all exceptions that occurred in all tasks in the given range.
template<class T, class InIt> 
void observe_all_exceptions(InIt first, InIt last) 
{
    std::for_each(first, last, [](concurrency::task<T> t)
    {
        t.then([](concurrency::task<T> previousTask)
        {
            try
            {
                previousTask.get();
            }
            // Although you could catch (...), this demonstrates how to catch specific exceptions. Your app
            // might handle different exception types in different ways.
            catch (Platform::Exception^)
            {
                // Swallow the exception.
            }
            catch (const std::exception&)
            {
                // Swallow the exception.
            }
        });
    });
}

C++ ve XAML kullanan ve diske bir dosya kümesi yazan bir UWP uygulaması düşünün. Aşağıdaki örnekte, programın tüm özel durumları gözlemlediğinden emin olmak için ve'nin observe_all_exceptions nasıl kullanılacağı when_all gösterilmektedir.

// Writes content to files in the provided storage folder.
// The first element in each pair is the file name. The second element holds the file contents.
task<void> MainPage::WriteFilesAsync(StorageFolder^ folder, const vector<pair<String^, String^>>& fileContents)
{
    // For each file, create a task chain that creates the file and then writes content to it. Then add the task chain to a vector of tasks.
    vector<task<void>> tasks;
    for (auto fileContent : fileContents)
    {
        auto fileName = fileContent.first;
        auto content = fileContent.second;

        // Create the file. The CreationCollisionOption::FailIfExists flag specifies to fail if the file already exists.
        tasks.emplace_back(create_task(folder->CreateFileAsync(fileName, CreationCollisionOption::FailIfExists)).then([content](StorageFile^ file)
        {
            // Write its contents.
            return create_task(FileIO::WriteTextAsync(file, content));
        }));
    }

    // When all tasks finish, create a continuation task that observes any exceptions that occurred.
    return when_all(begin(tasks), end(tasks)).then([tasks](task<void> previousTask)
    {
        task_status status = completed;
        try
        {
            status = previousTask.wait();
        }
        catch (COMException^ e)
        {
            // We'll handle the specific errors below.
        }
        // TODO: If other exception types might happen, add catch handlers here.

        // Ensure that we observe all exceptions.
        observe_all_exceptions<void>(begin(tasks), end(tasks));

        // Cancel any continuations that occur after this task if any previous task was canceled.
        // Although cancellation is not part of this example, we recommend this pattern for cases that do.
        if (status == canceled)
        {
            cancel_current_task();
        }
    });
}
Bu örneği çalıştırmak için
  1. MainPage.xaml dosyasında bir Button denetim ekleyin.
<Button x:Name="Button1" Click="Button_Click">Write files</Button>
  1. MainPage.xaml.h dosyasında, bu iletme bildirimlerini private sınıf bildiriminin MainPage bölümüne ekleyin.
void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
concurrency::task<void> WriteFilesAsync(Windows::Storage::StorageFolder^ folder, const std::vector<std::pair<Platform::String^, Platform::String^>>& fileContents);
  1. MainPage.xaml.cpp olay işleyicisini Button_Click uygulayın.
// A button click handler that demonstrates the scenario.
void MainPage::Button_Click(Object^ sender, RoutedEventArgs^ e)
{
    // In this example, the same file name is specified two times. WriteFilesAsync fails if one of the files already exists.
    vector<pair<String^, String^>> fileContents;
    fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 1")));
    fileContents.emplace_back(make_pair(ref new String(L"file2.txt"), ref new String(L"Contents of file 2")));
    fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 3")));

    Button1->IsEnabled = false; // Disable the button during the operation.
    WriteFilesAsync(ApplicationData::Current->TemporaryFolder, fileContents).then([this](task<void> previousTask)
    {
        try
        {
            previousTask.get();
        }
        // Although cancellation is not part of this example, we recommend this pattern for cases that do.
        catch (const task_canceled&)
        {
            // Your app might show a message to the user, or handle the error in some other way.
        }

        Button1->IsEnabled = true; // Enable the button.
    });
}
  1. MainPage.xaml.cpp'da örnekte gösterildiği gibi uygulayın WriteFilesAsync .

İpucu

when_all , sonucu olarak bir oluşturan engelleyici olmayan bir task işlevdir. Görev::wait'den farklı olarak, ASTA (Uygulama STA) iş parçacığındaki bir UWP uygulamasında bu işlevi çağırmak güvenlidir.

when_any İşlevi

İşlev, when_any bir görev kümesindeki ilk görev tamamlandığında tamamlanan bir görev oluşturur. Bu işlev, tamamlanan görevin sonucunu ve kümedeki bu görevin dizinini içeren bir std::p air nesnesi döndürür.

when_any İşlev özellikle aşağıdaki senaryolarda kullanışlıdır:

  • Yedekli işlemler. Birçok şekilde gerçekleştirilen bir algoritma veya işlem düşünün. önce biten işlemi seçmek ve ardından kalan işlemleri iptal etmek için işlevini kullanabilirsiniz when_any .

  • Dönüşümlü işlemler. Tümünün bitirmesi gereken birden çok işlem başlatabilir ve her işlem tamamlandıktan sonra sonuçları işlemek için işlevini kullanabilirsiniz when_any . Bir işlem tamamlandıktan sonra bir veya daha fazla ek görev başlatabilirsiniz.

  • Daraltılmış işlemler. eşzamanlı işlem sayısını sınırlayarak önceki senaryoyu genişletmek için işlevini kullanabilirsiniz when_any .

  • Süresi dolan işlemler. bir veya daha fazla görev ile belirli bir süreden sonra biten bir görev arasında seçim yapmak için işlevini kullanabilirsiniz when_any .

' when_allde olduğu gibi, bir görev kümesinin ilki tamamlandığında eylem gerçekleştirmesi gereken when_any bir devamlılık kullanmak yaygın bir durumdur. Aşağıdaki temel örnek, diğer üç görevin ilki tamamlandığında tamamlanan bir görev oluşturmak için kullanır when_any .

// select-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<int>, 3> tasks = {
        create_task([]() -> int { return 88; }),
        create_task([]() -> int { return 42; }),
        create_task([]() -> int { return 99; })
    };

    // Select the first to finish.
    when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
    {
        wcout << "First task to finish returns "
              << result.first
              << L" and has index "
              << result.second
              << L'.' << endl;
    }).wait();
}

/* Sample output:
    First task to finish returns 42 and has index 1.
*/

Bu örnekte, görev tabanlı bir devamlılık oluşturmayı da belirtebilirsiniz task<pair<int, size_t>> .

Not

gibi when_all, geçirdiğiniz when_any görevlerin tümü aynı türü döndürmelidir.

Söz dizimini || , aşağıdaki örnekte gösterildiği gibi bir dizi görevdeki ilk görev tamamlandıktan sonra tamamlayan bir görev oluşturmak için de kullanabilirsiniz.

auto t = t1 || t2; // same as when_any

İpucu

gibi when_all, when_any engelleyici değildir ve ASTA iş parçacığında bir UWP uygulamasında çağrı yapmak güvenlidir.

Gecikmeli Görev Yürütme

Bazen bir koşul karşılanana kadar görevin yürütülmesini geciktirmek veya bir dış olaya yanıt olarak bir görev başlatmak gerekir. Örneğin, zaman uyumsuz programlamada G/Ç tamamlama olayına yanıt olarak bir görev başlatmanız gerekebilir.

Bunu yapmanın iki yolu, bir devamlılık kullanmak veya bir görev başlatmak ve görevin iş işlevi içinde bir olayı beklemektir. Ancak, bu tekniklerden birinin kullanılmasının mümkün olmadığı durumlar vardır. Örneğin, bir devamlılık oluşturmak için öncül göreviniz olmalıdır. Ancak, öncül göreviniz yoksa, bir görev tamamlama olayı oluşturabilir ve daha sonra bu tamamlama olayını kullanılabilir olduğunda öncül göreve zincirleyebilirsiniz. Ayrıca, bekleyen bir görev de bir iş parçacığını engellediğinden, zaman uyumsuz bir işlem tamamlandığında işi gerçekleştirmek için görev tamamlama olaylarını kullanabilir ve böylece bir iş parçacığını serbest getirebilirsiniz.

Concurrency::task_completion_event sınıfı, bu tür görevlerin bileşimini basitleştirmeye yardımcı olur. task sınıfı gibi tür parametresi T de görev tarafından üretilen sonucun türüdür. Bu tür, görev bir değer döndürmezse olabilir void . T değiştiriciyi const kullanamaz. Genellikle, bir task_completion_event nesne için değer kullanılabilir olduğunda ona işaret edecek bir iş parçacığına veya göreve sağlanır. Aynı zamanda, bir veya daha fazla görev bu olayın dinleyicisi olarak ayarlanır. Olay ayarlandığında dinleyici görevleri tamamlanır ve devamları çalıştırılacak şekilde zamanlanır.

Bir gecikmeden sonra tamamlanan bir görevi uygulamak için kullanan task_completion_event bir örnek için bkz . Nasıl yapılır: Gecikmeden Sonra Tamamlanan Bir Görev Oluşturma.

Görev Grupları

Görev grubu bir görev koleksiyonunu düzenler. Görev grupları görevleri iş çalma kuyruğuna gönderir. Zamanlayıcı bu kuyruktan görevleri kaldırır ve kullanılabilir bilgi işlem kaynaklarında yürütür. Görev grubuna görev ekledikten sonra, tüm görevlerin tamamlanmasını bekleyebilir veya henüz başlatılmamış görevleri iptal edebilirsiniz.

PPL, görev gruplarını temsil etmek için eşzamanlılık::task_group ve eşzamanlılık::structured_task_group sınıflarını ve bu gruplarda çalışan görevleri temsil etmek için eşzamanlılık::task_handle sınıfını kullanır. sınıfı, task_handle iş gerçekleştiren kodu kapsüller. task sınıfı gibi iş işlevi de lambda işlevi, işlev işaretçisi veya işlev nesnesi biçiminde gelir. Genellikle nesnelerle task_handle doğrudan çalışmanız gerekmez. Bunun yerine, iş işlevlerini bir görev grubuna geçirirsiniz ve görev grubu nesneleri oluşturur ve yönetir task_handle .

PPL, görev gruplarını şu iki kategoriye ayırır: yapılandırılmamış görev grupları ve yapılandırılmış görev grupları. PPL, yapılandırılmamış görev gruplarını temsil etmek için sınıfını ve structured_task_group yapılandırılmış görev gruplarını temsil etmek için sınıfını kullanırtask_group.

Önemli

PPL ayrıca eşzamanlılık::p arallel_invoke algoritmasını structured_task_group tanımlar ve bu algoritma, bir dizi görevi paralel olarak yürütmek için sınıfını kullanır. Algoritmanın parallel_invoke daha kısa bir söz dizimi olduğundan, gerektiğinde sınıfı yerine bu söz dizimini structured_task_group kullanmanızı öneririz. Paralel Algoritmalar konusu daha ayrıntılı olarak açıklanmaktadırparallel_invoke.

Aynı anda yürütmek istediğiniz birkaç bağımsız göreviniz varsa kullanın parallel_invoke ve devam etmeden önce tüm görevlerin tamamlanmasını beklemeniz gerekir. Bu teknik genellikle çatal ve birleşim paralelliği olarak adlandırılır. Aynı anda yürütmek istediğiniz birkaç bağımsız göreviniz varsa, ancak görevlerin daha sonra bitmesini beklemek istediğinizde kullanın task_group . Örneğin, bir nesneye görev ekleyebilir ve görevlerin başka bir task_group işlevde veya başka bir iş parçacığından tamamlanmasını bekleyebilirsiniz.

Görev grupları iptal kavramını destekler. İptal etme, genel işlemi iptal etmek istediğiniz tüm etkin görevlere sinyal göndermenizi sağlar. İptal işlemi, henüz başlatılmamış görevlerin başlatılmasını da engeller. İptal hakkında daha fazla bilgi için bkz . PPL'de İptal.

Çalışma zamanı ayrıca bir görevden özel durum oluşturmanızı ve ilişkili görev grubunun tamamlanmasını beklerken bu özel durumu işlemenizi sağlayan bir özel durum işleme modeli sağlar. Bu özel durum işleme modeli hakkında daha fazla bilgi için bkz . Özel Durum İşleme.

task_group structured_task_group ile karşılaştırma

Sınıfı yerine structured_task_group veya parallel_invoke kullanmanızı task_group öneririz, ancak kullanmak istediğiniz structured_task_groupdurumlar vardır; örneğin, değişken sayıda görev gerçekleştiren veya iptal için destek gerektiren paralel bir algoritma yazdığınızda. Bu bölümde ve structured_task_group sınıfları arasındaki farklar task_group açıklanmaktadır.

task_group sınıfı iş parçacığı açısından güvenlidir. Bu nedenle, bir task_group nesneye birden çok iş parçacığından görev ekleyebilir ve bir nesneyi birden çok iş parçacığından bekleyebilir veya iptal edebilirsiniz task_group . Bir structured_task_group nesnenin oluşturulması ve yok edilmesi aynı sözcük temelli kapsamda gerçekleşmelidir. Ayrıca, bir structured_task_group nesnedeki tüm işlemler aynı iş parçacığında gerçekleşmelidir. Bu kuralın özel durumu eşzamanlılık::structured_task_group::cancel ve eşzamanlılık::structured_task_group::is_canceling yöntemleridir. Alt görev, üst görev grubunu iptal etmek veya herhangi bir zamanda iptal olup olmadığını denetlemek için bu yöntemleri çağırabilir.

Concurrency::task_group::wait veya concurrency::task_group::run_and_wait yöntemini çağırdıktan sonra nesne task_group üzerinde ek görevler çalıştırabilirsiniz. Buna karşılık, concurrency::structured_task_group::wait veya concurrency::structured_task_group::run_and_wait yöntemlerini çağırdıktan sonra nesne structured_task_group üzerinde ek görevler çalıştırırsanız, davranış tanımlanmamış olur.

structured_task_group Sınıfı iş parçacıkları arasında eşitlenmediğinden, yürütme yükü sınıfından task_group daha azdır. Bu nedenle, sorununuz birden çok iş parçacığından iş zamanlamanızı gerektirmiyorsa ve algoritmayı parallel_invoke kullanamıyorsanız, structured_task_group sınıfı daha iyi performans gösteren kod yazmanıza yardımcı olabilir.

Bir nesneyi başka bir structured_task_group structured_task_group nesnenin içinde kullanırsanız, dış nesne bitmeden önce iç nesnenin bitip yok edilmesi gerekir. sınıfı, task_group iç içe görev gruplarının dış grup tamamlanmadan önce bitmesi için gerekli değildir.

Yapılandırılmamış görev grupları ve yapılandırılmış görev grupları, görev tanıtıcılarıyla farklı şekillerde çalışır. İş işlevlerini doğrudan bir task_group nesneye geçirebilirsiniz; task_group nesne sizin için görev tutamacını oluşturur ve yönetir. structured_task_group sınıfı, her görev için bir task_handle nesne yönetmenizi gerektirir. Her task_handle nesne, ilişkili structured_task_group nesnesinin ömrü boyunca geçerli kalmalıdır. Aşağıdaki temel örnekte gösterildiği gibi concurrency::make_task işlevini kullanarak bir task_handle nesne oluşturun:

// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>

using namespace concurrency;

int wmain()
{
   // Use the make_task function to define several tasks.
   auto task1 = make_task([] { /*TODO: Define the task body.*/ });
   auto task2 = make_task([] { /*TODO: Define the task body.*/ });
   auto task3 = make_task([] { /*TODO: Define the task body.*/ });

   // Create a structured task group and run the tasks concurrently.

   structured_task_group tasks;

   tasks.run(task1);
   tasks.run(task2);
   tasks.run_and_wait(task3);
}

Değişken sayıda göreviniz olduğu durumlarda görev tanıtıcılarını yönetmek için _malloca gibi bir yığın ayırma yordamı veya std::vector gibi bir kapsayıcı sınıfı kullanın.

structured_task_group Hem hem de task_group iptali destekler. İptal hakkında daha fazla bilgi için bkz . PPL'de İptal.

Örnek

Aşağıdaki temel örnekte görev gruplarıyla çalışma gösterilmektedir. Bu örnek, iki görevi eşzamanlı olarak gerçekleştirmek için algoritmayı parallel_invoke kullanır. Her görev bir task_group nesneye alt görevler ekler. task_group sınıfının, birden çok görev tarafından buna eşzamanlı olarak görev eklenmesine izin verdiğine dikkat edin.

// using-task-groups.cpp
// compile with: /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>

using namespace concurrency;
using namespace std;

// Prints a message to the console.
template<typename T>
void print_message(T t)
{
   wstringstream ss;
   ss << L"Message from task: " << t << endl;
   wcout << ss.str(); 
}

int wmain()
{  
   // A task_group object that can be used from multiple threads.
   task_group tasks;

   // Concurrently add several tasks to the task_group object.
   parallel_invoke(
      [&] {
         // Add a few tasks to the task_group object.
         tasks.run([] { print_message(L"Hello"); });
         tasks.run([] { print_message(42); });
      },
      [&] {
         // Add one additional task to the task_group object.
         tasks.run([] { print_message(3.14); });
      }
   );

   // Wait for all tasks to finish.
   tasks.wait();
}

Bu örnekte örnek çıktı aşağıda verilmiştir:

Message from task: Hello
Message from task: 3.14
Message from task: 42

parallel_invoke Algoritma görevleri eşzamanlı olarak çalıştırdığından, çıkış iletilerinin sırası farklılık gösterebilir.

Algoritmanın parallel_invoke nasıl kullanılacağını gösteren eksiksiz örnekler için bkz . How to: Use parallel_invoke to Write a Parallel Sort Routine and How to: Use parallel_invoke to Execute Parallel Operations. Zaman uyumsuz vadeli işlemleri uygulamak için sınıfını task_group kullanan eksiksiz bir örnek için bkz . İzlenecek Yol: Vadeli İşlemleri Uygulama.

Güçlü Programlama

Görevler, görev grupları ve paralel algoritmalar kullanırken iptal ve özel durum işlemenin rolünü anladığınızdan emin olun. Örneğin, paralel çalışma ağacında, iptal edilen bir görev alt görevlerin çalışmasını engeller. Alt görevlerden biri uygulamanız için önemli olan bir işlemi gerçekleştirirse (örneğin, bir kaynağı boşaltma) bu sorunlara neden olabilir. Buna ek olarak, bir alt görev özel durum oluşturursa, bu özel durum bir nesne yıkıcı aracılığıyla yayılabilir ve uygulamanızda tanımsız davranışa neden olabilir. Bu noktaları gösteren bir örnek için, Paralel Desen kitaplığındaki en iyi yöntemler belgesindeki İptal ve Özel Durum İşlemenin Nesne Yok Edilmesini Nasıl Etkilediğini Anlama bölümüne bakın. PPL'deki iptal ve özel durum işleme modelleri hakkında daha fazla bilgi için bkz . İptal ve Özel Durum İşleme.

Ünvan Açıklama
Nasıl yapılır: Paralel Sıralama Rutini Yazmak için parallel_invoke Kullanma Bitonik sıralama algoritmasının parallel_invoke performansını geliştirmek için algoritmanın nasıl kullanılacağını gösterir.
Nasıl yapılır: Paralel İşlemleri Yürütmek için parallel_invoke Kullanma Paylaşılan bir veri kaynağında parallel_invoke birden çok işlem gerçekleştiren bir programın performansını geliştirmek için algoritmanın nasıl kullanılacağını gösterir.
Nasıl yapılır: Bir Gecikmeden Sonra Tamamlanan bir Görev Oluşturma Bir gecikmeden tasksonra tamamlanan bir görev oluşturmak için , cancellation_token_source, cancellation_tokenve task_completion_event sınıflarının nasıl kullanılacağını gösterir.
İzlenecek Yol: Vadeli İşlemleri Uygulama Eşzamanlılık Çalışma Zamanı'ndaki mevcut işlevlerin daha fazlasını yapabilen bir işlemle nasıl birleştirildiğini gösterir.
Paralel Desen Kitaplığı (PPL) Eşzamanlı uygulamalar geliştirmek için kesinlik temelli bir programlama modeli sağlayan PPL'yi açıklar.

Başvuru

task Sınıfı (Eşzamanlılık Çalışma Zamanı)

task_completion_event Sınıfı

when_all İşlevi

when_any İşlevi

task_group Sınıfı

parallel_invoke İşlevi

structured_task_group Sınıfı