Zadania zewnętrzne i ziarna
Zgodnie z projektem wszystkie podzadania zduplikowane z kodu ziarna (na przykład przy użyciu lub await
ContinueWith
Task.Factory.StartNew
) będą wysyłane na taką samą aktywację TaskScheduler co zadanie nadrzędne i dlatego dziedziczą ten sam model wykonywania jednowątkowego co reszta kodu ziarna. Jest to główny punkt związany z wykonywaniem jednowątkowego współbieżności opartej na ziarnie.
W niektórych przypadkach może być konieczne "podzielenie" Orleans modelu planowania zadań i "wykonanie czegoś specjalnego", na przykład jawne wskazanie Task
innego harmonogramu zadań lub platformy .NET ThreadPool. Przykładem takiego przypadku jest to, że kod ziarna musi wykonać synchroniczne zdalne wywołanie blokujące (np. zdalne we/wy). Wykonanie tego blokującego wywołania w kontekście ziarna spowoduje zablokowanie ziarna 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 i sprzężenia (await
) ukończenia tego wykonania i kontynuować w kontekście ziarna. Oczekujemy, że ucieczka z Orleans harmonogramu będzie bardzo zaawansowanym i rzadko wymaganym scenariuszem użycia poza "normalnymi" wzorcami użycia.
Interfejsy API oparte na zadaniach
await, TaskFactory.StartNew (patrz poniżej), Task.ContinueWith, Task.WhenAny, Task.WhenAll, Task.Delay wszystkie z uwzględnieniem bieżącego harmonogramu zadań. Oznacza to, że użycie ich w domyślny sposób bez przekazywania innego TaskSchedulerelementu spowoduje ich wykonanie w kontekście ziarna.
Zarówno Task.Run , jak i
endMethod
pełnomocnik TaskFactory.FromAsync nie przestrzegają bieżącego harmonogramu zadań. Obaj używająTaskScheduler.Default
harmonogramu, który jest harmonogramem zadań puli wątków platformy .NET. W związku z tym kod wewnątrzTask.Run
i wewnątrzTask.Factory.FromAsync
endMethod
będzie zawsze uruchamiany w puli wątków platformy .NET poza modelem wykonywania jednowątkowego dla Orleans ziarna. Jednak każdy kod poawait Task.Run
kodzie lubawait Task.Factory.FromAsync
zostanie uruchomiony z powrotem w harmonogramie w momencie utworzenia zadania, który jest harmonogramem ziarna.Task.ConfigureAwait with
false
jest jawnym interfejsem API umożliwiającym ucieczkę bieżącego harmonogramu zadań. Spowoduje to wykonanie kodu po oczekiwanym zadaniu w harmonogramie TaskScheduler.Default , czyli puli wątków platformy .NET, co spowoduje przerwanie jednowątkowego wykonywania ziarna.Uwaga
Ogólnie rzecz biorąc , nigdy nie należy używać
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 ulegać awarii bieżącego procesu, jeśli zezwalają na wyjątek ucieczki bez możliwości obsługi wyjątku. Dotyczy to równieżList<T>.ForEach(async element => ...)
i każdej innej metody, która akceptuje Action<T>element , ponieważ pełnomocnik asynchroniczny zostanie coerced do delegataasync void
.
Task.Factory.StartNew
i async
delegatów
Zwykle zaleca się planowanie zadań w dowolnym programie języka C# na Task.Run
rzecz Task.Factory.StartNew
programu . Szybkie wyszukiwanie google na temat użycia Task.Factory.StartNew
sugeruje, że jest niebezpieczne i że powinien zawsze faworyzować Task.Run
. Ale jeśli chcemy pozostać w modelu wykonywania ziarna jednowątkowego dla naszego ziarna, musimy go użyć, więc jak to zrobić poprawnie? Niebezpieczeństwo użycia Task.Factory.StartNew()
polega na tym, że nie obsługuje natywnie delegatów asynchronicznych. Oznacza to, że prawdopodobnie jest to usterka: var notIntendedTask = Task.Factory.StartNew(SomeDelegateAsync)
. notIntendedTask
nie jest zadaniem, które kończy się w takim przypadkuSomeDelegateAsync
. Zamiast tego należy zawsze odpakowywać zwrócone zadanie: var task = Task.Factory.StartNew(SomeDelegateAsync).Unwrap()
.
Przykład wielu zadań i harmonogramu zadań
Poniżej przedstawiono przykładowy kod, który demonstruje użycie , i specjalnego niestandardowego TaskScheduler.Current
Task.Run
harmonogramu, aby uciec z Orleans kontekstu ziarna i jak wrócić do niego.
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łania ziarna z kodu uruchamianego w puli wątków
Innym scenariuszem jest fragment kodu ziarna, który musi "rozbić" model planowania zadań ziarna i uruchomić w puli wątków (lub inny kontekst, inny niż ziarno), ale nadal musi wywołać inne ziarno. Wywołania ziarna można wykonywać z kontekstów innych niż ziarna bez dodatkowej ceremonii.
Poniżej przedstawiono kod, który pokazuje, jak można wykonać wywołanie ziarna z fragmentu kodu uruchamianego wewnątrz ziarna, ale nie w kontekście ziarna.
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, których używa kod, mogą używać ConfigureAwait(false)
wewnętrznie. Dobrym i poprawnym rozwiązaniem jest użycie platformy ConfigureAwait(false)
.NET podczas implementowania bibliotek ogólnego przeznaczenia. Nie jest to problem w pliku Orleans. O ile kod w ziarnie, który wywołuje metodę biblioteki, oczekuje na wywołanie biblioteki z regularnym await
kodem ziarna jest poprawny. Wynik będzie dokładnie taki, jak jest to pożądane — kod biblioteki będzie uruchamiał kontynuacje w domyślnym harmonogramie (wartość zwrócona przez TaskScheduler.Default
element , która nie gwarantuje, że kontynuacje będą uruchamiane w ThreadPool wątku, ponieważ kontynuacje są często wbudowane w poprzednim wątku), podczas gdy kod ziarna będzie uruchamiany w harmonogramie ziarna.
Innym często zadawanym pytaniem jest to, czy istnieje potrzeba wykonania wywołań biblioteki za Task.Run
pomocą — czyli czy istnieje potrzeba jawnego odciążania kodu biblioteki do ThreadPool
(aby kod ziarna to zrobić Task.Run(() => myLibrary.FooAsync())
). Odpowiedź brzmi nie. Nie ma potrzeby odciążania żadnego kodu ThreadPool
z wyjątkiem przypadku kodu biblioteki, który tworzy blokujące wywołania synchroniczne. Zwykle każda dobrze napisana i poprawna biblioteka asynchronicka platformy .NET (metody zwracające Task
i są nazwane sufiksem Async
) nie powoduje wywołań blokujących. W związku z tym nie ma potrzeby odciążania niczego, ThreadPool
chyba że podejrzewasz, że biblioteka asynchroniczna jest usterka lub jeśli celowo używasz synchronicznej biblioteki blokującej.
Zakleszczenia
Ponieważ ziarna są wykonywane w sposób jednowątkowy, istnieje możliwość zakleszczenia ziarna przez synchroniczne blokowanie w sposób, który wymagałby odblokowania wielu wątków. Oznacza to, że kod, który wywołuje dowolną z następujących metod i właściwości, może zakleszczać ziarno, jeśli podane zadania nie zostały jeszcze ukończone przez czas wywołania metody lub właściwości:
Task.Wait()
Task.Result
Task.WaitAny(...)
Task.WaitAll(...)
task.GetAwaiter().GetResult()
Te metody należy unikać w dowolnej usłudze o wysokiej współbieżności, ponieważ mogą one prowadzić do niskiej wydajności i niestabilności przez głodowanie platformy .NET ThreadPool
przez blokowanie wątków, które mogą wykonywać przydatne zadania i wymagają od platformy .NET ThreadPool
wstrzyknięcia dodatkowych wątków, aby można je było ukończyć. Podczas wykonywania kodu ziarna te metody, jak wspomniano powyżej, mogą spowodować zakleszczenie ziarna i dlatego należy unikać ich również w kodzie ziarna.
Jeśli nie można uniknąć pewnej pracy synchronizacji za pośrednictwem asynchronicznego , najlepiej przenieść ją do oddzielnego harmonogramu. Najprostszym sposobem, aby to zrobić, jest await Task.Run(() => task.Wait())
użycie na przykład. Należy pamiętać, że zdecydowanie zaleca się unikanie pracy synchronizacji za pośrednictwem asynchronicznego , ponieważ, jak wspomniano powyżej, spowoduje to spadek skalowalności i wydajności aplikacji.
Podsumowanie pracy 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. |