I have some library (socket networking) code that provides a Task
-based API for pending responses to requests, based on TaskCompletionSource<T>
. However, there's an annoyance in the TPL in that it seems to be impossible to prevent synchronous continuations. What I would like to be able to do is either:
- tell a
TaskCompletionSource<T>
that is should not allow callers to attach withTaskContinuationOptions.ExecuteSynchronously
, or - set the result (
SetResult
/TrySetResult
) in a way that specifies thatTaskContinuationOptions.ExecuteSynchronously
should be ignored, using the pool instead
Specifically, the issue I have is that the incoming data is being processed by a dedicated reader, and if a caller can attach with TaskContinuationOptions.ExecuteSynchronously
they can stall the reader (which affects more than just them). Previously, I have worked around this by some hackery that detects whether any continuations are present, and if they are it pushes the completion onto the ThreadPool
, however this has significant impact if the caller has saturated their work queue, as the completion will not get processed in a timely fashion. If they are using Task.Wait()
(or similar), they will then essentially deadlock themselves. Likewise, this is why the reader is on a dedicated thread rather than using workers.
So; before I try and nag the TPL team: am I missing an option?
Key points:
- I don't want external callers to be able to hijack my thread
- I can't use the
ThreadPool
as an implementation, as it needs to work when the pool is saturated
The example below produces output (ordering may vary based on timing):
Continuation on: Main thread
Press [return]
Continuation on: Thread pool
The problem is the fact that a random caller managed to get a continuation on "Main thread". In the real code, this would be interrupting the primary reader; bad things!
Code:
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
with my own API to prevent direct call toContinueWith
, since neitherTaskCompletionSource
, norTask
doesn't suit well for inheritance from them. – DennisTask
that is exposed, not theTaskCompletionSource
. That (exposing a different API) is technically an option, but it is a pretty extreme thing to do just for this... I'm not sure it justifies it – Marc Gravell♦ThreadPool
for this (which I already mentioned - it causes problems), or you have a dedicated "pending continuations" thread, and then they (continations withExecuteSynchronously
specified) can hijack that one instead - which causes exactly the same problem, because it means that continuations for other messages can be stalled, which again impacts multiple callers – Marc Gravell♦