Mam kod biblioteki (sieci gniazd), który zapewnia Task
oparty na interfejsie API dla oczekujących odpowiedzi na żądania, w oparciu o TaskCompletionSource<T>
. Jednak w TPL występuje irytacja polegająca na tym, że wydaje się niemożliwe zapobiec synchronicznym kontynuacjom. Co ja lubię być w stanie zrobić, to albo:
- powiedz,
TaskCompletionSource<T>
że nie powinno pozwalać dzwoniącym na dołączanie się za pomocąTaskContinuationOptions.ExecuteSynchronously
lub - ustaw wynik (
SetResult
/TrySetResult
) w sposób, który określa, żeTaskContinuationOptions.ExecuteSynchronously
powinien być ignorowany, używając zamiast tego puli
W szczególności problem, który mam, polega na tym, że przychodzące dane są przetwarzane przez dedykowany czytnik i jeśli dzwoniący może się przyłączyć TaskContinuationOptions.ExecuteSynchronously
, może zablokować czytnik (co dotyczy nie tylko ich). Wcześniej obejrzałem to przez hakera, który wykrywa, czy są obecne jakieś kontynuacje, a jeśli tak, wypycha ukończenie do ThreadPool
, jednak ma to znaczący wpływ, jeśli dzwoniący wypełnił swoją kolejkę pracy, ponieważ zakończenie nie zostanie przetworzone w odpowiednim czasie. Jeśli używają Task.Wait()
(lub czegoś podobnego), będą w zasadzie zablokować się. Dlatego też czytelnik znajduje się w dedykowanym wątku, a nie korzysta z pracowników.
Więc; zanim spróbuję zganić zespół TPL: czy brakuje mi opcji?
Kluczowe punkty:
- Nie chcę, aby rozmówcy zewnętrzni mogli przejąć mój wątek
- Nie mogę użyć
ThreadPool
implementacji, ponieważ musi działać, gdy pula jest nasycona
Poniższy przykład daje wynik (kolejność może się różnić w zależności od czasu):
Continuation on: Main thread
Press [return]
Continuation on: Thread pool
Problem w tym, że przypadkowemu dzwoniącemu udało się uzyskać kontynuację w "Głównym wątku". W prawdziwym kodzie byłoby to zakłóceniem podstawowego czytelnika; złe rzeczy!
Kod:
using System;
using System.Threading;
using System.Threading.Tasks;
static class Program
{
static void Identify()
{
var thread = Thread.CurrentThread;
string name = thread.IsThreadPoolThread
? "Thread pool" : thread.Name;
if (string.IsNullOrEmpty(name))
name = "#" + thread.ManagedThreadId;
Console.WriteLine("Continuation on: " + name);
}
static void Main()
{
Thread.CurrentThread.Name = "Main thread";
var source = new TaskCompletionSource<int>();
var task = source.Task;
task.ContinueWith(delegate {
Identify();
});
task.ContinueWith(delegate {
Identify();
}, TaskContinuationOptions.ExecuteSynchronously);
source.TrySetResult(123);
Console.WriteLine("Press [return]");
Console.ReadLine();
}
}
TaskCompletionSource
własnym API, aby zapobiec bezpośredniemu wywoływaniuContinueWith
, ponieważ ani nieTaskCompletionSource
, aniTask
nie nadają się dobrze do dziedziczenia po nich.