Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Zgodnie z projektem wszystkie podzadania pochodzące z kodu ziarna (np. przy użyciu await
, ContinueWith
lub Task.Factory.StartNew
) przekazują do tej samej aktywacji TaskScheduler co zadanie nadrzędne. W związku z tym dziedziczą ten sam model wykonywania jednowątkowego co pozostała część kodu ziarna. Jest to główny punkt związany z wykonywaniem jednowątkowego współbieżności opartej na ziarnie.
W niektórych przypadkach kod ziarna może wymagać "wyrwania się" z Orleans modelu planowania zadań i "zrobienia czegoś specjalnego", takiego jak wyraźne wskazanie Task
innego harmonogramu zadań lub platformy .NET ThreadPool. Przykładem jest sytuacja, gdy kod ziarnisty musi wykonać synchroniczne zdalne wywołanie blokujące (np. zdalne I/O). Wykonanie tego wywołania blokującego w kontekście ziarna blokuje ziarno i w ten sposób nigdy nie powinno być wykonywane. Zamiast tego kod ziarna może wykonać ten fragment kodu blokującego w wątku puli wątków, poczekać na zakończenie tego wykonania, a następnie kontynuować w kontekście ziarna. Oczekuje się, że wyjście poza Orleans harmonogram będzie bardzo zaawansowanym i rzadko wymaganym scenariuszem użycia, wykraczającym poza typowe wzorce stosowania.
Interfejsy API oparte na zadaniach
await
, TaskFactory.StartNew (patrz poniżej), Task.ContinueWith, Task.WhenAny, Task.WhenAll i Task.Delay są zgodne z bieżącym harmonogramem zadań. Oznacza to, że użycie ich w domyślny sposób bez przekazywania innego TaskSchedulerelementu powoduje ich wykonanie w kontekście ziarna.Zarówno Task.Run , jak i
endMethod
pełnomocnik TaskFactory.FromAsyncnie przestrzegają bieżącego harmonogramu zadań. Obaj używająTaskScheduler.Default
harmonogramu , harmonogramu zadań puli wątków platformy .NET. W związku z tym kod wewnątrzTask.Run
orazendMethod
wTask.Factory.FromAsync
zawsze jest uruchamiany na puli wątków platformy .NET, poza jednowątkowym modelem wykonawczym dla ziaren Orleans. Jednak każdy kod poawait Task.Run
lubawait Task.Factory.FromAsync
jest uruchamiany ponownie pod kontrolą harmonogramu aktywnego w momencie utworzenia zadania, czyli harmonogramu ziarna.Task.ConfigureAwait with
false
jest jawnym interfejsem API umożliwiającym ucieczkę bieżącego harmonogramu zadań. Kod po oczekiwaniu naTask
wykonuje się w harmonogramie TaskScheduler.Default (puli wątków platformy .NET), przez co zostaje przerwane jednowątkowe wykonywanie ziarna.Uwaga
Ogólnie rzecz biorąc, nigdy nie używaj
ConfigureAwait(false)
bezpośrednio w kodzie ziarna.Metody z podpisem
async void
nie powinny być używane z ziarnami. Są one przeznaczone dla graficznych procedur obsługi zdarzeń interfejsu użytkownika.async void
Metoda może natychmiast spowodować awarię obecnego procesu, jeśli pozwoli na ucieczkę wyjątku, bez możliwości jego obsłużenia. Dotyczy to równieżList<T>.ForEach(async element => ...)
oraz każdej innej metody akceptującej Action<T>, ponieważ asynchroniczny delegat przekształca się w delegataasync void
.
Task.Factory.StartNew
i async
delegatów
Zwykle zaleca się używanie Task.Run
zamiast Task.Factory.StartNew
do planowania zadań w języku C#. Szybkie wyszukiwanie Task.Factory.StartNew
w Internecie sugeruje , że jest to niebezpieczne i faworyzowanie Task.Run
jest zawsze zalecane. Jednak aby pozostać w modelu wykonywania ziarna w jednym wątku, Task.Factory.StartNew
należy użyć. Jak więc używać go poprawnie? Zagrożeniem związanym z Task.Factory.StartNew()
jest brak natywnego wsparcia dla delegatów asynchronicznych. Oznacza to, że kod podobny var notIntendedTask = Task.Factory.StartNew(SomeDelegateAsync)
jest prawdopodobnie usterką.
notIntendedTask
nie jest zadaniem, które kończy się, gdy SomeDelegateAsync
się zakończy. Zamiast tego zawsze odpakuj zwrócone zadanie: var task = Task.Factory.StartNew(SomeDelegateAsync).Unwrap()
.
Przykład: wiele zadań i harmonogram zadań
Poniżej przedstawiono przykładowy kod demonstrujący użycie TaskScheduler.Current
, Task.Run
i specjalnego niestandardowego harmonogramu, aby wyjść z kontekstu ziarna i jak do niego powrócić Orleans.
public async Task MyGrainMethod()
{
// Grab the grain's task scheduler
var orleansTS = TaskScheduler.Current;
await Task.Delay(10_000);
// Current task scheduler did not change, the code after await is still running
// in the same task scheduler.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
Task t1 = Task.Run(() =>
{
// This code runs on the thread pool scheduler, not on Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
Assert.AreEqual(TaskScheduler.Default, TaskScheduler.Current);
});
await t1;
// We are back to the Orleans task scheduler.
// Since await was executed in Orleans task scheduler context, we are now back
// to that context.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
// Example of using Task.Factory.StartNew with a custom scheduler to escape from
// the Orleans scheduler
Task t2 = Task.Factory.StartNew(() =>
{
// This code runs on the MyCustomSchedulerThatIWroteMyself scheduler, not on
// the Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
Assert.AreEqual(MyCustomSchedulerThatIWroteMyself, TaskScheduler.Current);
},
CancellationToken.None,
TaskCreationOptions.None,
scheduler: MyCustomSchedulerThatIWroteMyself);
await t2;
// We are back to Orleans task scheduler.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
}
Przykład: wywołanie funkcji grain w kodzie uruchomionym w wątku z puli wątków
Inny scenariusz obejmuje kod ziarna, który musi "przerwać" model planowania zadań dla ziarna i uruchomić go w wątku puli wątków (lub w kontekście niezwiązanym z ziarnem), ale nadal musi wywołać kolejne ziarno. Wywołania ziarna można wykonywać z kontekstów innych niż ziarna bez dodatkowej ceremonii.
Poniższy kod demonstruje wykonywanie wywołania na ziarnie z kodu uruchomionego wewnątrz ziarna, ale poza jego kontekstem.
public async Task MyGrainMethod()
{
// Grab the Orleans task scheduler
var orleansTS = TaskScheduler.Current;
var fooGrain = this.GrainFactory.GetGrain<IFooGrain>(0);
Task<int> t1 = Task.Run(async () =>
{
// This code runs on the thread pool scheduler,
// not on Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
int res = await fooGrain.MakeGrainCall();
// This code continues on the thread pool scheduler,
// not on the Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
return res;
});
int result = await t1;
// We are back to the Orleans task scheduler.
// Since await was executed in the Orleans task scheduler context,
// we are now back to that context.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
}
Praca z bibliotekami
Niektóre biblioteki zewnętrzne używane przez kod mogą używać ConfigureAwait(false)
wewnętrznie. Użycie ConfigureAwait(false)
jest dobrym i poprawnym rozwiązaniem w środowisku .NET podczas implementowania bibliotek ogólnego przeznaczenia. Nie jest to problem w pliku Orleans. Dopóki kod ziarna wywołujący metodę biblioteki oczekuje na wywołanie tej biblioteki w regularny sposób, kod ziarna jest poprawny. Wynik jest dokładnie taki, jak jest to pożądane: kod biblioteki uruchamia kontynuacje w domyślnym harmonogramie (wartość zwracana przez TaskScheduler.Default
, która nie gwarantuje kontynuacji uruchamianych w wątku ThreadPool, ponieważ często są wykonywane w poprzednim wątku), podczas gdy kod ziarna działa w harmonogramie ziarna.
Innym często poruszanym pytaniem jest to, czy wywołania biblioteki wymagają wykonania poprzez Task.Run
— czyli czy kod biblioteki wymaga jawnego odciążenia na element ThreadPool
(np. await Task.Run(() => myLibrary.FooAsync())
). Odpowiedź brzmi nie. Odciążanie kodu do ThreadPool
nie jest konieczne, z wyjątkiem sytuacji, gdy kod biblioteki powoduje blokujące wywołania synchroniczne. Zwykle każda dobrze napisana i poprawna biblioteka asynchroniczna platformy .NET (metody zwracające Task
i nazwane z sufiksem Async
) nie tworzy wywołań blokujących. Podsumowując, odciążanie czegokolwiek do ThreadPool
nie jest potrzebne, chyba że podejrzewa się, że biblioteka asynchroniczna jest wadliwa lub celowo używa się synchronizującej biblioteki blokującej.
Zakleszczenia
Ponieważ ziarna wykonują pojedyncze wątki, zakleszczenie ziarna jest możliwe przez synchroniczne blokowanie w sposób wymagający odblokowywania wielu wątków. Oznacza to, że kod wywołujący dowolną z następujących metod i właściwości może zablokować element (grain), jeśli przekazane zadania nie zostały ukończone przed wywołaniem metody lub właściwości.
Task.Wait()
Task.Result
Task.WaitAny(...)
Task.WaitAll(...)
task.GetAwaiter().GetResult()
Unikaj tych metod w dowolnej usłudze o wysokiej współbieżności, ponieważ mogą one prowadzić do niskiej wydajności i niestabilności. Głodują platformę .NET ThreadPool
, blokując wątki, które mogą wykonać przydatną pracę i wymagają ThreadPool
wprowadzenia dodatkowych wątków do ukończenia. Podczas wykonywania kodu ziarna te metody mogą spowodować zakleszczenie ziarna, więc unikaj ich również w kodzie ziarna.
Jeśli pewna część pracy związanej z synchronizacją powyżej asynchronicznej jest nieunikniona, najlepiej jest przenieść tę pracę do oddzielnego schedulera. Najprostszym sposobem jest na przykład użycie metody await Task.Run(() => task.Wait())
. Należy pamiętać, że zdecydowanie zaleca się unikanie pracy w trybie synchronizacja nad asynchronicznością, ponieważ szkodzi to skalowalności i wydajności aplikacji.
Podsumowanie: Praca z zadaniami w programie Orleans
Co próbujesz zrobić? | Jak to zrobić |
---|---|
Uruchom pracę w tle w wątkach puli wątków platformy .NET. Nie są dozwolone żadne wywołania kodu ziarna ani ziarna. | Task.Run |
Uruchom zadanie asynchronicznego procesu roboczego z poziomu kodu ziarna z gwarancjami Orleans współbieżności opartymi na kolei (zobacz powyżej). |
Task.Factory.StartNew(WorkerAsync).Unwrap() (Unwrap) |
Uruchom synchroniczne zadanie procesu roboczego z poziomu kodu ziarna z gwarancjami Orleans współbieżności opartej na kolei. | Task.Factory.StartNew(WorkerSync) |
Limity czasu wykonywania elementów roboczych | Task.Delay + Task.WhenAny |
Wywoływanie metody biblioteki asynchronicznej |
await wywołanie biblioteki |
Korzystanie z polecenia async /await |
Normalny model programowania .NET Task-Async. Obsługiwane i zalecane |
ConfigureAwait(false) |
Nie używaj kodu ziarna wewnętrznego. Dozwolone tylko wewnątrz bibliotek. |