Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Az egyidejűségi futtatókörnyezetben a tevékenységek olyan munkaegységek, amelyek egy adott feladatot hajtanak végre, és általában más tevékenységekkel párhuzamosan futnak. A tevékenységek további, részletesebb tevékenységekre bonthatók, amelyek egy tevékenységcsoportba vannak rendezve.
Az aszinkron kód írásakor feladatokat használ, és azt szeretné, hogy az aszinkron művelet befejeződése után valamilyen művelet történjen. Egy feladat használatával például aszinkron módon olvashat egy fájlból, majd használhat egy másik feladatot – a dokumentum későbbi részében ismertetett folytatási feladatot – az adatok feldolgozásához, miután az elérhetővé válik. Ezzel szemben a tevékenységek csoportjaival kisebb részekre bonthatja a párhuzamos munkát. Tegyük fel például, hogy van egy rekurzív algoritmusa, amely a fennmaradó munkát két partícióra osztja. Feladatcsoportokkal egyszerre futtathatja ezeket a partíciókat, majd megvárhatja, amíg az osztott munka befejeződik.
Jótanács
Ha ugyanazt a rutint szeretné alkalmazni egy gyűjtemény minden elemére párhuzamosan, használjon párhuzamos algoritmust, például egyidejűséget::p arallel_for, feladat vagy feladatcsoport helyett. A párhuzamos algoritmusokról további információt a Párhuzamos algoritmusok című témakörben talál.
Kulcsfontosságú pontok
Ha referencia alapján ad át változókat egy lambda kifejezésnek, garantálnia kell, hogy a változó élettartama a tevékenység befejezéséig megmarad.
Aszinkron kód írásakor használjon feladatokat ( az egyidejűség::feladatosztályt ). A feladatosztály a Windows ThreadPoolt használja ütemezőként, nem pedig az egyidejűségi futtatókörnyezetet.
Használjon tevékenységcsoportokat (a párhuzamosság::task_group osztályt vagy a párhuzamosság::parallel_invoke algoritmust), ha a párhuzamos munkát kisebb darabokra szeretné bontani, majd várja meg, amíg a kisebb részek befejeződnek.
A folytatások létrehozásához használja a concurrency::task::then metódust. A folytatás olyan tevékenység, amely aszinkron módon fut egy másik tevékenység befejezése után. Tetszőleges számú folytatást csatlakoztathat aszinkron munkalánc létrehozásához.
A tevékenységalapú folytatás mindig végrehajtásra van ütemezve, amikor az előzménytevékenység befejeződik, még akkor is, ha az előzményfeladatot megszakítják vagy kivételt vetnek ki.
Használja a concurrency::when_all-t olyan feladat létrehozására, amely akkor fejeződik be, amikor egy feladatkészlet minden tagja befejeződik. A(z) concurrency::when_any használatával olyan feladatot hozhat létre, amely befejeződik, miután egy feladatkészlet egyik tagja befejeződik.
A feladatok és a feladatcsoportok részt vehetnek a párhuzamos minták könyvtárának (PPL) megszakítási mechanizmusában. További információ: Lemondás a PPL-ben.
Ha tudni szeretné, hogy a futtatókörnyezet hogyan kezeli a tevékenységek és tevékenységcsoportok által kidobott kivételeket, tekintse meg a Kivételkezelés című témakört.
Ebben a dokumentumban
Lambda-kifejezések használata
Tömör szintaxisuk miatt a lambda kifejezések gyakran definiálják a tevékenységek és tevékenységcsoportok által végzett munkát. Íme néhány használati tipp:
Mivel a feladatok általában háttérszálakon futnak, érdemes tisztában lenni az objektum élettartamával, amikor változókat rögzít a Lambda-kifejezésekben. Amikor érték szerint rögzít egy változót, a rendszer másolatot készít a változóról a lambda törzsében. Ha hivatkozás alapján rögzíti, a rendszer nem készít másolatot. Ezért győződjön meg arról, hogy a hivatkozással rögzített változók élettartama túllépi az azt használó feladatot.
Amikor lambda kifejezést ad át egy feladatnak, ne rögzítse a veremen hivatkozással lefoglalt változókat.
Legyen explicit a lambda kifejezésekben rögzített változókkal kapcsolatban, hogy érték és hivatkozás alapján azonosíthassa a rögzített értékeket. Ezért azt javasoljuk, hogy ne használja a
[=]vagy[&]beállításokat lambda kifejezésekhez.
Gyakori minta, ha egy folytonos lánc egyik feladata egy változóhoz rendel hozzá, egy másik feladat pedig beolvassa ezt a változót. Nem rögzíthető érték szerint, mert minden folytatási tevékenység a változó egy másik példányát tárolná. A veremmemóriában elhelyezett változók esetében nem lehet hivatkozással rögzíteni, mert lehet, hogy a változó már nem érvényes.
A probléma megoldásához használjon egy intelligens mutatót(például std::shared_ptr) a változó körbefuttatásához, és adja át az intelligens mutatót érték szerint. Ily módon a mögöttes objektum hozzárendelhető és onnan olvasható, és az azt használó feladatokat ki fogja haladni. Ezt a technikát akkor is használhatja, ha a változó egy Windows futtatókörnyezeti objektum mutatója vagy hivatkozásszámlált leírója (^). Íme egy egyszerű példa:
// 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
*/
További információ a lambdakifejezésekről: Lambda Expressions.
A tevékenységosztály
A concurrency::task osztályt használva feladatokat kombinálhatsz egy függő műveletek sorozatába. Ezt a kompozíciós modellt a folytatások fogalma is támogatja. A folytatás lehetővé teszi a kód végrehajtását, amikor az előző vagy az előzményfeladat befejeződik. Az előzményfeladat eredménye egy vagy több folytatási tevékenység bemeneteként lesz átadva. Ha egy előzményfeladat befejeződik, a rá váró összes folytatási tevékenység végrehajtásra lesz ütemezve. Minden folytatási tevékenység megkapja az előzményfeladat eredményének másolatát. Ezek a folytatási tevékenységek más folytatásokhoz is lehetnek előzményfeladatok, ezáltal tevékenységláncot hozhatnak létre. A folytatások segítségével tetszőleges hosszúságú tevékenységláncokat hozhat létre, amelyek között meghatározott függőségek vannak. Emellett a feladatok részt vehetnek a lemondásban akár a kezdésük előtt, akár együttműködő módon a futásuk alatt. További információ erről a lemondási modellről: Lemondás a PPL-ben.
task egy sablonosztály. A típusparaméter T a tevékenység által előállított eredmény típusa. Ez a típus akkor lehet void , ha a tevékenység nem ad vissza értéket.
T nem használható a const módosító.
Amikor létrehoz egy feladatot, egy munkafüggvényt ad meg, amely végrehajtja a feladat törzsét. Ez a munkafüggvény lambdafüggvény, függvénymutató vagy függvényobjektum formájában jelenik meg. Ha megvárja, hogy egy feladat befejeződjön az eredmény elérése nélkül, hívja meg a concurrency::task::wait metódust. A task::wait metódus egy concurrency::task_status értéket ad vissza, amely leírja, hogy a feladat befejeződött vagy megszakítva lett-e. A feladat eredményének lekéréséhez hívja meg a concurrency::task::get metódust. Ez a metódus arra kéri task::wait , hogy várja meg a feladat befejezését, ezért letiltja az aktuális szál végrehajtását, amíg az eredmény el nem érhető.
Az alábbi példa bemutatja, hogyan hozhat létre egy tevékenységet, várhatja meg az eredményt, és hogyan jelenítheti meg az értékét. A jelen dokumentációban szereplő példák lambdafüggvényeket használnak, mert tömörebb szintaxist biztosítanak. A feladatok használatakor azonban függvénymutatókat és függvényobjektumokat is használhat.
// 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
*/
Az concurrency::create_task függvény használatakor a auto kulcsszót használhatja a típus deklarálása helyett. Vegyük például ezt a kódot, amely létrehozza és kinyomtatja az identitásmátrixot:
// 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
*/
A create_task függvény használatával létrehozhatja az egyenértékű műveletet.
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;
});
Ha egy feladat végrehajtása során kivétel keletkezik, a futtatókörnyezet a kivételt a következő task::get vagy task::wait hívásban, vagy egy feladatalapú folytatásban kezeli. A feladatkivétel-kezelési mechanizmusról további információt a Kivételkezelés című témakörben talál.
Ha például a következőt használjatask: egyidejűség::task_completion_event, lemondás, lásd: Útmutató: Csatlakozás feladatok és XML HTTP-kérések használatával. (Az task_completion_event osztályt a dokumentum későbbi részében ismertetjük.)
Jótanács
Az UWP-alkalmazások feladataira vonatkozó részletekért tekintse meg a C++ aszinkron programozását és az Aszinkron műveletek létrehozását a C++-ban az UWP-alkalmazásokhoz.
Folytatási tevékenységek
Az aszinkron programozásban nagyon gyakori, hogy egy aszinkron művelet befejeztével meghív egy második műveletet, és adatokat ad át neki. Ez hagyományosan visszahívási módszerekkel történik. Az egyidejűségi futtatókörnyezetben ugyanezt a funkciót a folytatási feladatok biztosítják. A folytatási tevékenység (más néven folytatás) egy aszinkron tevékenység, amelyet egy másik tevékenység hív meg, amelyet antecedentnek neveznek, amikor az előzmény befejeződik. A folytatások használatával a következőt teheti:
Adatok továbbítása az előzményből a folytatásba.
Adja meg a folytatás meghívásának vagy nem meghívásának pontos feltételeit.
A folytatást akár a kezdés előtt, akár együttműködéssel megszakíthatja, amíg fut.
Adjon meg tippeket a folytatás ütemezéséről. (Ez csak az univerzális Windows Platform (UWP) alkalmazásokra vonatkozik. További információ: Aszinkron műveletek létrehozása C++ nyelven UWP-alkalmazásokhoz.)
Több folytatást hívni ugyanabból az előzményből.
Egyetlen folytatás meghívása, ha több előzmény mindegyike vagy bármelyike befejeződött.
A lánc egymás után folytatódik, bármilyen hosszúságúra.
A folytatással kezelheti az előzmény által kidobott kivételeket.
Ezek a funkciók lehetővé teszik egy vagy több feladat végrehajtását az első tevékenység befejezésekor. Létrehozhat például egy folytatást, amely tömöríti a fájlokat, miután az első feladat beolvassa azt a lemezről.
Az alábbi példa módosítja az előzőt az egyidejűség::feladat::majd a folytatás ütemezésére szolgáló módszerre, amely az előzményfeladat értékét nyomtatja ki, ha az elérhető.
// 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
*/
Tetszőleges hosszúságig láncolhatja és beágyazhatja a feladatokat. Egy tevékenységnek több folytatása is lehet. Az alábbi példa egy alapszintű folytatási láncot mutat be, amely háromszor növeli az előző tevékenység értékét.
// 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
*/
A folytatás egy másik feladatot is visszaadhat. Ha nincs lemondás, a rendszer a következő folytatás előtt végrehajtja ezt a feladatot. Ezt a technikát aszinkron feloldásnak nevezzük. Az aszinkron feloldás akkor hasznos, ha további munkát szeretne végezni a háttérben, de nem szeretné, hogy az aktuális tevékenység blokkolja az aktuális szálat. (Ez gyakori az UWP-alkalmazásokban, ahol a folytatások a felhasználói felületen futtathatók). Az alábbi példa három tevékenységet mutat be. Az első tevékenység egy másik, a folytatási tevékenység előtt futtatott tevékenységet ad vissza.
// 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
*/
Fontos
Ha egy tevékenység folytatása egy beágyazott típusú feladatot Nad vissza, az eredményül kapott tevékenység típusa Nnem task<N>, és a beágyazott tevékenység befejezésekor fejeződik be. Más szóval a folytatás végrehajtja a beágyazott feladat kibontását.
Value-Based és Task-Based folytatások
Egy task objektum esetén, amelynek visszatérési típusa T, megadhat egy T vagy task<T> típusú értéket a folytatásfeladataiban. A típust T használó folytatásokat értékalapú folytatásnak nevezzük. Az értékalapú folytatás akkor lesz végrehajtásra ütemezve, ha az előzményfeladat hiba nélkül befejeződik, és nincs megszakítva. A paraméterként megadott típusú task<T> folytatást feladatalapú folytatásnak nevezzük. A tevékenységalapú folytatás mindig végrehajtásra van ütemezve, amikor az előzménytevékenység befejeződik, még akkor is, ha az előzményfeladatot megszakítják vagy kivételt vetnek ki. Ezután meghívhatja task::get annak érdekében, hogy megkapja az előzményfeladat eredményét. Ha az előzményfeladatot törölték, task::getconcurrency::task_canceled-t dob. Ha az előzményfeladat kivételt jelzett, task::get újra dobja azt a kivételt. A feladatalapú folytatás nincs jelölve töröltnek, ha az előzményfeladatot törlik.
Feladatok összeállítása
Ez a szakasz az egyidejűséget::when_all és egyidejűséget::when_any függvényeket ismerteti, amelyek segíthetnek több feladat összeállításában a gyakori minták implementálásához.
A when_all függvény
A when_all függvény egy olyan feladatot hoz létre, amely egy tevékenységkészlet befejezése után fejeződik be. Ez a függvény egy std::vector objektumot ad vissza, amely a készlet egyes feladatainak eredményét tartalmazza. Az alábbi egyszerű példa when_all-t használ egy feladat létrehozására, amely három másik feladat befejezését jelképezi.
// 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.
*/
Megjegyzés:
Az when_all átadandó feladatoknak egységesnek kell lenniük. Más szóval mindegyiknek ugyanazt a típust kell visszaadnia.
A szintaxissal && olyan feladatot is létrehozhat, amely egy feladatkészlet befejezése után fejeződik be, ahogyan az alábbi példában is látható.
auto t = t1 && t2; // same as when_all
Gyakori, hogy egy folytatást együtt használunk a when_all-val, hogy elvégezzünk egy műveletet egy feladatsor befejeződése után. Az alábbi példa módosítja az előzőt, hogy kinyomtassa három feladat eredményének az összegét, amelyek mindegyike int eredményt ad.
// 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.
*/
Ebben a példában azt is megadhatja task<vector<int>> , hogy tevékenységalapú folytatást hoz-e létre.
Ha egy tevékenységcsoport valamelyik tevékenységét megszakítják, vagy kivételt okoznak, when_all azonnal befejeződik, és nem várja meg, amíg a fennmaradó tevékenységek befejeződnek. Amennyiben egy kivétel keletkezik, a futtatókörnyezet újradobja a kivételt, amikor a task::get vagy task::wait metódust hívja meg a visszaadott when_all feladatobjektumon. Ha több feladat esetén hiba keletkezik, a futtatókörnyezet választ egyet közülük. Ezért győződjön meg arról, hogy az összes tevékenység befejezése után minden kivételt figyelembe kell vennie; a nem kezelt tevékenység kivétele miatt az alkalmazás leáll.
Íme egy segédprogramfüggvény, amellyel biztosíthatja, hogy a program megfigyelje az összes kivételt. A megadott tartomány minden egyes tevékenységéhez observe_all_exceptions újradobja az előforduló kivételt, majd lenyeli azt.
// 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.
}
});
});
}
Fontolja meg egy olyan UWP-alkalmazást, amely C++ és XAML protokollt használ, és fájlokat ír lemezre. Az alábbi példa bemutatja, hogyan használható when_all és observe_all_exceptions annak biztosítására, hogy a program figyelembe vegye az összes kivételt.
// 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();
}
});
}
A példa futtatása
- A MainPage.xaml fájlban adjon hozzá egy vezérlőt
Button.
<Button x:Name="Button1" Click="Button_Click">Write files</Button>
- A MainPage.xaml.h fájlban adja hozzá ezeket a továbbítási deklarációkat az
privateMainPageosztálydeklaráció szakaszához.
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);
- Az MainPage.xaml.cpp implementálja az
Button_Clickeseménykezelőt.
// 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.
});
}
- A(z) MainPage.xaml.cpp-ben valósítsa meg a
WriteFilesAsyncelemet, ahogy az a példában látható.
Jótanács
when_all egy nem blokkoló függvény, amely ennek eredményeként task hoz létre. Ellentétben feladat::wait, nyugodtan meghívhatja ezt a függvényt egy UWP-alkalmazásban az ASTA (Application STA) szálon.
A when_any függvény
A when_any függvény létrehoz egy feladatot, amely akkor fejeződik be, amikor egy tevékenységcsoport első tevékenysége befejeződik. Ez a függvény egy std::pair objektumot ad vissza, amely a befejezett feladat eredményét és a feladat indexét tartalmazza a halmazban.
A when_any függvény különösen hasznos az alábbi esetekben:
Redundáns műveletek. Vegyünk egy algoritmust vagy műveletet, amely sokféleképpen végrehajtható. A függvény használatával
when_anykiválaszthatja az elsőként befejezett műveletet, majd megszakíthatja a fennmaradó műveleteket.Keveredett műveletek. Több olyan műveletet is elindíthat, amelyeknek mindegyiknek be kell fejeződnie, és a függvény használatával feldolgozhatja az
when_anyeredményeket az egyes műveletek befejezésekor. Miután egy művelet befejeződött, elindíthat egy vagy több további feladatot.Korlátozott műveletek. A függvény használatával
when_anykibővítheti az előző forgatókönyvet az egyidejű műveletek számának korlátozásával.Lejárt műveletek. A függvény használatával
when_anyválaszthat egy vagy több tevékenység és egy adott idő után befejezett tevékenység között.
Ahogy when_all esetében, gyakori, hogy olyan folytatást használunk, amely when_any-t használ, hogy műveletet hajtson végre, amikor az első feladat befejeződik egy feladatkészleten belül. Az alábbi egyszerű példa when_any egy olyan feladatot hoz létre, amely három másik tevékenység közül az első befejezésekor fejeződik be.
// 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.
*/
Ebben a példában azt is megadhatja task<pair<int, size_t>> , hogy tevékenységalapú folytatást hoz-e létre.
Megjegyzés:
Ahogy a when_all-hoz, a when_any-nak átadott feladatoknak ugyanolyan típust kell visszaadniuk.
A szintaxissal || olyan feladatot is létrehozhat, amely egy tevékenységcsoport első tevékenysége után fejeződik be, ahogyan az alábbi példában látható.
auto t = t1 || t2; // same as when_any
Jótanács
Ahogy a when_all, a when_any sem blokkoló, és biztonságosan meghívható egy UWP-alkalmazásban az ASTA-szálon.
Késleltetett feladatvégrehajtás
Néha szükség van egy feladat végrehajtásának késleltetésére, amíg egy feltétel teljesül, vagy egy tevékenységet külső eseményre válaszul kell elindítani. Az aszinkron programozásban például előfordulhat, hogy egy I/O-befejezési eseményre válaszul el kell indítania egy feladatot.
Ennek két módja a folytatás használata, vagy egy tevékenység indítása és egy esemény várakozása a tevékenység munkafüggvényén belül. Vannak azonban olyan esetek, amikor nem lehet ezen technikák egyikét használni. Ha például egy folytatást szeretne létrehozni, rendelkeznie kell az előzményfeladatával. Ha azonban nem rendelkezik az előzményfeladatokkal, létrehozhat egy feladat-befejezési eseményt , majd később láncba láncosíthatja a befejezési eseményt az előzménytevékenységhez, amikor az elérhetővé válik. Emellett, mivel egy várakozó tevékenység egy szálat is blokkol, a feladatkiegészítési események használatával elvégezheti a munkát az aszinkron művelet befejezésekor, és ezáltal felszabadíthat egy szálat.
Az konkurencia::task_completion_event osztály segít leegyszerűsíteni az ilyen típusú feladatok összeállítását. Az task osztályhoz hasonlóan a típusparaméter T a tevékenység által előállított eredmény típusa. Ez a típus akkor lehet void , ha a tevékenység nem ad vissza értéket.
T nem használható a const módosító. Általában egy task_completion_event objektumot adnak át egy szálnak vagy feladatnak, amely jelzi, amikor az értéke elérhetővé válik. Ugyanakkor egy vagy több feladat van beállítva az esemény figyelőiként. Amikor az esemény be van állítva, a hallgatói tevékenységek befejeződnek, és a folytatások ütemezett végrehajtásra kerülnek.
Egy feladat késleltetett befejezésének implementálására szolgáló példát a task_completion_event használatával megtekintheti a Hogyan lehet: Késés után befejezett feladat létrehozása című témakörben.
Tevékenységcsoportok
Egy feladatcsoport feladatgyűjteményt szervez. A feladatcsoportok egy munkalopó várakozási sorba küldik a feladatokat. Az ütemező eltávolítja a feladatokat a sorból, és végrehajtja őket a rendelkezésre álló számítási erőforrásokon. Miután hozzáadta a tevékenységeket egy tevékenységcsoporthoz, megvárhatja, amíg az összes tevékenység befejeződik, vagy megszakítja azokat a tevékenységeket, amelyek még nem kezdődtek el.
A PPL az egyidejűség::task_group és egyidejűség::structured_task_group osztályok használatával jelöli a tevékenységcsoportokat, az egyidejűség::task_handle osztály pedig az ezekben a csoportokban futó tevékenységeket. Az task_handle osztály beágyazza a munkát végző kódot. Az task osztályhoz hasonlóan a munkafüggvény lambdafüggvény, függvénymutató vagy függvényobjektum formájában is jelenik meg. Önnek általában nem kell közvetlenül task_handle objektumokkal dolgoznia. Ehelyett munkafolyamatokat ad át egy feladatcsoportnak, és a feladatcsoport létrehozza és kezeli az task_handle objektumokat.
A PPL a tevékenységcsoportokat a következő két kategóriába osztja: strukturálatlan tevékenységcsoportokra és strukturált tevékenységcsoportokra. A PPL a task_group osztályt strukturálatlan tevékenységcsoportok, a structured_task_group osztályt pedig strukturált tevékenységcsoportok ábrázolására használja.
Fontos
A PPL az egyidejűség::parallel_invoke algoritmust is meghatározza, amely az structured_task_group osztály használatával feladatok végrehajtását párhuzamosan hajtja végre. Mivel az parallel_invoke algoritmusnak tömörebb a szintaxisa, azt javasoljuk, hogy lehetőség szerint a structured_task_group osztály helyett azt használja. A Párhuzamos algoritmusok témakör részletesebben is leírja parallel_invoke .
Akkor érdemes használni parallel_invoke , ha több független tevékenységet szeretne egyszerre végrehajtani, és a folytatás előtt meg kell várnia, amíg az összes tevékenység befejeződik. Ezt a technikát gyakran elágazás és összeillesztés párhuzamosságnak nevezik. Akkor érdemes használni task_group , ha több független tevékenységet szeretne egyszerre végrehajtani, de várnia kell, amíg a tevékenységek egy későbbi időpontban befejeződnek. Hozzáadhat például tevékenységeket egy task_group objektumhoz, és megvárhatja, amíg a tevékenységek befejeződnek egy másik függvényben vagy egy másik szálon.
A tevékenységcsoportok támogatják a lemondás fogalmát. A lemondással jelezheti az összes aktív tevékenységnek, hogy le szeretné mondani az általános műveletet. A lemondás megakadályozza azokat a feladatokat is, amelyek még nem kezdődtek el. További információ a lemondásról: Lemondás a PPL-ben.
A futtatókörnyezet egy kivételkezelési modellt is biztosít, amely lehetővé teszi, hogy kivételt adjon ki egy tevékenységből, és kezelje a kivételt, amikor megvárja, amíg a társított tevékenységcsoport befejeződik. A kivételkezelési modellről további információt a Kivételkezelés című témakörben talál.
A task_group és a structured_task_group összehasonlítása
Bár azt javasoljuk, hogy használja a task_group vagy parallel_invoke helyett az structured_task_group osztályt, vannak olyan esetek, amikor a structured_task_group használatát szeretné, például amikor olyan párhuzamos algoritmust ír, amely változó számú feladatot hajt végre, vagy támogatást igényel a lemondáshoz. Ez a szakasz az task_group és structured_task_group osztályok közötti különbségeket ismerteti.
Az task_group osztály szálbiztos. Ezért egy task_group objektumhoz több szálból is hozzáadhat feladatokat, valamint több szálból várakozhat vagy megszakíthat egy task_group objektumot. Egy objektum felépítésének structured_task_group és megsemmisítésének ugyanabban a lexikális hatókörben kell történnie. Emellett az structured_task_group objektumokon végzett összes műveletnek ugyanazon a szálon kell történnie. A szabály alól kivételt képeznek a concurrency::structured_task_group::cancel és concurrency::structured_task_group::is_canceling metódusok. A gyermekfeladat meghívhatja ezeket a metódusokat a szülőfeladat-csoport megszakítására, vagy bármikor ellenőrizheti a megszakítást.
Az task_group vagy concurrency::task_group::run_and_wait metódus meghívása után további feladatokat is futtathat egy objektumon. Ezzel szemben, ha az structured_task_group vagy egyidejűség::structured_task_group::run_and_wait metódusok meghívása után további feladatokat futtat egy objektumon, akkor a viselkedés nincs meghatározva.
Mivel az structured_task_group osztály nem szinkronizálja a szálakat, kevesebb végrehajtási többletterheléssel rendelkezik, mint az task_group osztály. Ezért ha a probléma nem követeli meg, hogy több szálból ütemezzen munkát, és nem tudja használni az parallel_invoke algoritmust, az structured_task_group osztály segíthet a kód jobb végrehajtásában.
Ha egy objektumot structured_task_group egy másik structured_task_group objektumon belül használ, a belső objektumnak be kell fejeződnie, és el kell pusztítania, mielőtt a külső objektum befejeződik. Az task_group osztály nem követeli meg, hogy a beágyazott tevékenységcsoportok a külső csoport befejeződése előtt befejeződjenek.
A strukturálatlan tevékenységcsoportok és a strukturált tevékenységcsoportok különböző módokon működnek a feladatkezelőkkel. A munkafüggvényeket közvetlenül átadhatja egy task_group objektumnak; az task_group objektum létrehozza és kezeli a feladatkezelőt. Az structured_task_group osztály megköveteli, hogy minden tevékenységhez egy objektumot kezeljen task_handle . Minden task_handle objektumnak érvényesnek kell maradnia a társított structured_task_group objektum teljes élettartama alatt. A concurrency::make_task függvénnyel hozzon létre egy task_handle objektumot, ahogy azt az alábbi egyszerű példa mutatja be:
// 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);
}
Az olyan esetek feladatkezelőinek kezeléséhez, amelyekben változó számú tevékenység van, használjon olyan halomfoglalási rutint, mint például a _malloca vagy egy tárolóosztály, például az std::vector.
Mind task_group, mind structured_task_group támogatja a lemondást. További információ a lemondásról: Lemondás a PPL-ben.
példa
Az alábbi egyszerű példa bemutatja, hogyan dolgozhat a tevékenységcsoportokkal. Ez a példa az parallel_invoke algoritmus használatával egyszerre két feladatot hajt végre. Minden tevékenység altevékenységeket ad hozzá egy task_group objektumhoz. Vegye figyelembe, hogy az task_group osztály lehetővé teszi több tevékenység egyidejű hozzáadását.
// 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();
}
A példához a következő mintakimenet tartozik:
Message from task: Hello
Message from task: 3.14
Message from task: 42
Mivel az parallel_invoke algoritmus párhuzamosan futtatja a feladatokat, a kimeneti üzenetek sorrendje eltérő lehet.
Az algoritmus használatát parallel_invoke szemléltető teljes példákért lásd : Útmutató: A parallel_invoke használata párhuzamos rendezési rutin írásához és útmutató: A parallel_invoke használata párhuzamos műveletek végrehajtásához. Egy teljes példa, amely az task_group osztályt használja aszinkron jövők implementálásához, tekintse meg az útmutatót: Futures implementálása.
Robusztus programozás
Győződjön meg arról, hogy tisztában van a lemondás és a kivételkezelés szerepkörével, amikor feladatokat, feladatcsoportokat és párhuzamos algoritmusokat használ. A párhuzamos munka fájában például a leállított feladat megakadályozza a gyermekfeladatok futását. Ez problémákat okozhat, ha az egyik gyermekfeladat olyan műveletet hajt végre, amely fontos az alkalmazás számára, például felszabadít egy erőforrást. Emellett, ha egy gyermekfeladat kivételt okoz, ez a kivétel egy objektumdestruktoron keresztül propagálható, és meghatározatlan viselkedést okozhat az alkalmazásban. A fenti szempontokat szemléltető példáért tekintse meg a Párhuzamos minták kódtár dokumentumának ajánlott eljárásainak Az objektummegsemmisítésre gyakorolt hatásának megértése című szakaszát. A PPL lemondási és kivételkezelési modelljeiről további információt a Lemondás és a Kivételkezelés című témakörben talál.
Kapcsolódó témakörök
| Cím | Leírás |
|---|---|
| Útmutató: Párhuzamos rendezési rutin írása parallel_invoke használatával | Bemutatja, hogyan javíthatja a parallel_invoke bitonikus rendezési algoritmus teljesítményét az algoritmus használatával. |
| Útmutató: Párhuzamos műveletek végrehajtása parallel_invoke használatával | Bemutatja, hogyan javíthatja az algoritmust parallel_invoke egy olyan program teljesítményének javítására, amely több műveletet hajt végre egy megosztott adatforráson. |
| Útmutató: Késés után befejezett feladat létrehozása | Bemutatja, hogyan hozhat létre késleltetés után befejezett feladatot a task, cancellation_token_source, cancellation_tokenés task_completion_event osztályokkal. |
| Útmutató: Futures implementálása | Azt mutatja be, hogyan kombinálhatja a párhuzamosság-futtatókörnyezet meglévő funkcióit olyan funkciókkal, amelyek többre is használhatók. |
| Párhuzamos minták kódtára (PPL) | Ismerteti a PPL-t, amely imperatív programozási modellt biztosít az egyidejű alkalmazások fejlesztéséhez. |