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 with TaskContinuationOptions.ExecuteSynchronously, or
set the result (SetResult / TrySetResult) in a way that specifies that TaskContinuationOptions.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();
}
}
New in .NET 4.6:
.NET 4.6 contains a new TaskCreationOptions: RunContinuationsAsynchronously.
Since you're willing to use Reflection to access private fields...
You can mark the TCS's Task with the TASK_STATE_THREAD_WAS_ABORTED flag, which would cause all continuations not to be inlined.
const int TASK_STATE_THREAD_WAS_ABORTED = 134217728;
var stateField = typeof(Task).GetField("m_stateFlags", BindingFlags.NonPublic | BindingFlags.Instance);
stateField.SetValue(task, (int) stateField.GetValue(task) | TASK_STATE_THREAD_WAS_ABORTED);
Edit:
Instead of using Reflection emit, I suggest you use expressions. This is much more readable and has the advantage of being PCL-compatible:
var taskParameter = Expression.Parameter(typeof (Task));
const string stateFlagsFieldName = "m_stateFlags";
var setter =
Expression.Lambda<Action<Task>>(
Expression.Assign(Expression.Field(taskParameter, stateFlagsFieldName),
Expression.Or(Expression.Field(taskParameter, stateFlagsFieldName),
Expression.Constant(TASK_STATE_THREAD_WAS_ABORTED))), taskParameter).Compile();
Without using Reflection:
If anyone's interested, I've figured out a way to do this without Reflection, but it is a bit "dirty" as well, and of course carries a non-negligible perf penalty:
try
{
Thread.CurrentThread.Abort();
}
catch (ThreadAbortException)
{
source.TrySetResult(123);
Thread.ResetAbort();
}
I don't think there's anything in TPL which would provides explicit API control over TaskCompletionSource.SetResult continuations. I decided to keep my initial answer for controlling this behavior for async/await scenarios.
Here is another solution which imposes asynchronous upon ContinueWith, if the tcs.SetResult-triggered continuation takes place on the same thread the SetResult was called on:
public static class TaskExt
{
static readonly ConcurrentDictionary<Task, Thread> s_tcsTasks =
new ConcurrentDictionary<Task, Thread>();
// SetResultAsync
static public void SetResultAsync<TResult>(
this TaskCompletionSource<TResult> #this,
TResult result)
{
s_tcsTasks.TryAdd(#this.Task, Thread.CurrentThread);
try
{
#this.SetResult(result);
}
finally
{
Thread thread;
s_tcsTasks.TryRemove(#this.Task, out thread);
}
}
// ContinueWithAsync, TODO: more overrides
static public Task ContinueWithAsync<TResult>(
this Task<TResult> #this,
Action<Task<TResult>> action,
TaskContinuationOptions continuationOptions = TaskContinuationOptions.None)
{
return #this.ContinueWith((Func<Task<TResult>, Task>)(t =>
{
Thread thread = null;
s_tcsTasks.TryGetValue(t, out thread);
if (Thread.CurrentThread == thread)
{
// same thread which called SetResultAsync, avoid potential deadlocks
// using thread pool
return Task.Run(() => action(t));
// not using thread pool (TaskCreationOptions.LongRunning creates a normal thread)
// return Task.Factory.StartNew(() => action(t), TaskCreationOptions.LongRunning);
}
else
{
// continue on the same thread
var task = new Task(() => action(t));
task.RunSynchronously();
return Task.FromResult(task);
}
}), continuationOptions).Unwrap();
}
}
Updated to address the comment:
I don't control the caller - I can't get them to use a specific
continue-with variant: if I could, the problem would not exist in the
first place
I wasn't aware you don't control the caller. Nevertheless, if you don't control it, you're probably not passing the TaskCompletionSource object directly to the caller, either. Logically, you'd be passing the token part of it, i.e. tcs.Task. In which case, the solution might be even easier, by adding another extension method to the above:
// ImposeAsync, TODO: more overrides
static public Task<TResult> ImposeAsync<TResult>(this Task<TResult> #this)
{
return #this.ContinueWith(new Func<Task<TResult>, Task<TResult>>(antecedent =>
{
Thread thread = null;
s_tcsTasks.TryGetValue(antecedent, out thread);
if (Thread.CurrentThread == thread)
{
// continue on a pool thread
return antecedent.ContinueWith(t => t,
TaskContinuationOptions.None).Unwrap();
}
else
{
return antecedent;
}
}), TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}
Use:
// library code
var source = new TaskCompletionSource<int>();
var task = source.Task.ImposeAsync();
// ...
// client code
task.ContinueWith(delegate
{
Identify();
}, TaskContinuationOptions.ExecuteSynchronously);
// ...
// library code
source.SetResultAsync(123);
This actually works for both await and ContinueWith (fiddle) and is free of reflection hacks.
What about instead of doing
var task = source.Task;
you do this instead
var task = source.Task.ContinueWith<Int32>( x => x.Result );
Thus you are always adding one continuation which will be executed asynchronously and then it doesn't matter if the subscribers want a continuation in the same context. It's sort of currying the task, isn't it?
The simulate abort approach looked really good, but led to the TPL hijacking threads in some scenarios.
I then had an implementation that was similar to checking the continuation object, but just checking for any continuation since there are actually too many scenarios for the given code to work well, but that meant that even things like Task.Wait resulted in a thread-pool lookup.
Ultimately, after inspecting lots and lots of IL, the only safe and useful scenario is the SetOnInvokeMres scenario (manual-reset-event-slim continuation). There are lots of other scenarios:
some aren't safe, and lead to thread hijacking
the rest aren't useful, as they ultimately lead to the thread-pool
So in the end, I opted to check for a non-null continuation-object; if it is null, fine (no continuations); if it is non-null, special-case check for SetOnInvokeMres - if it is that: fine (safe to invoke); otherwise, let the thread-pool perform the TrySetComplete, without telling the task to do anything special like spoofing abort. Task.Wait uses the SetOnInvokeMres approach, which is the specific scenario we want to try really hard not to deadlock.
Type taskType = typeof(Task);
FieldInfo continuationField = taskType.GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.NonPublic);
Type safeScenario = taskType.GetNestedType("SetOnInvokeMres", BindingFlags.NonPublic);
if (continuationField != null && continuationField.FieldType == typeof(object) && safeScenario != null)
{
var method = new DynamicMethod("IsSyncSafe", typeof(bool), new[] { typeof(Task) }, typeof(Task), true);
var il = method.GetILGenerator();
var hasContinuation = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, continuationField);
Label nonNull = il.DefineLabel(), goodReturn = il.DefineLabel();
// check if null
il.Emit(OpCodes.Brtrue_S, nonNull);
il.MarkLabel(goodReturn);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Ret);
// check if is a SetOnInvokeMres - if so, we're OK
il.MarkLabel(nonNull);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, continuationField);
il.Emit(OpCodes.Isinst, safeScenario);
il.Emit(OpCodes.Brtrue_S, goodReturn);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
IsSyncSafe = (Func<Task, bool>)method.CreateDelegate(typeof(Func<Task, bool>));
if you can and are ready to use reflection, this should do it;
public static class MakeItAsync
{
static public void TrySetAsync<T>(this TaskCompletionSource<T> source, T result)
{
var continuation = typeof(Task).GetField("m_continuationObject", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
var continuations = (List<object>)continuation.GetValue(source.Task);
foreach (object c in continuations)
{
var option = c.GetType().GetField("m_options", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
var options = (TaskContinuationOptions)option.GetValue(c);
options &= ~TaskContinuationOptions.ExecuteSynchronously;
option.SetValue(c, options);
}
source.TrySetResult(result);
}
}
Updated, I posted a separate answer to deal with ContinueWith as opposed to await (because ContinueWith doesn't care about the current synchronization context).
You could use a dumb synchronization context to impose asynchrony upon continuation triggered by calling SetResult/SetCancelled/SetException on TaskCompletionSource. I believe the current synchronization context (at the point of await tcs.Task) is the criteria TPL uses to decide whether to make such continuation synchronous or asynchronous.
The following works for me:
if (notifyAsync)
{
tcs.SetResultAsync(null);
}
else
{
tcs.SetResult(null);
}
SetResultAsync is implemented like this:
public static class TaskExt
{
static public void SetResultAsync<T>(this TaskCompletionSource<T> tcs, T result)
{
FakeSynchronizationContext.Execute(() => tcs.SetResult(result));
}
// FakeSynchronizationContext
class FakeSynchronizationContext : SynchronizationContext
{
private static readonly ThreadLocal<FakeSynchronizationContext> s_context =
new ThreadLocal<FakeSynchronizationContext>(() => new FakeSynchronizationContext());
private FakeSynchronizationContext() { }
public static FakeSynchronizationContext Instance { get { return s_context.Value; } }
public static void Execute(Action action)
{
var savedContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(FakeSynchronizationContext.Instance);
try
{
action();
}
finally
{
SynchronizationContext.SetSynchronizationContext(savedContext);
}
}
// SynchronizationContext methods
public override SynchronizationContext CreateCopy()
{
return this;
}
public override void OperationStarted()
{
throw new NotImplementedException("OperationStarted");
}
public override void OperationCompleted()
{
throw new NotImplementedException("OperationCompleted");
}
public override void Post(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Post");
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Send");
}
}
}
SynchronizationContext.SetSynchronizationContext is very cheap in terms of the overhead it adds. In fact, a very similar approach is taken by the implementation of WPF Dispatcher.BeginInvoke.
TPL compares the target synchronization context at the point of await to that of the point of tcs.SetResult. If the synchronization context is the same (or there is no synchronization context at both places), the continuation is called directly, synchronously. Otherwise, it's queued using SynchronizationContext.Post on the target synchronization context, i.e., the normal await behavior. What this approach does is always impose the SynchronizationContext.Post behavior (or a pool thread continuation if there's no target synchronization context).
Updated, this won't work for task.ContinueWith, because ContinueWith doesn't care about the current synchronization context. It however works for await task (fiddle). It also does work for await task.ConfigureAwait(false).
OTOH, this approach works for ContinueWith.
Related
We have an old 3rd party system (let's call it Junksoft® 95) that we interface with via PowerShell (it exposes a COM object) and I'm in the process of wrapping it in a REST API (ASP.NET Framework 4.8 and WebAPI 2). I use the System.Management.Automation nuget package to create a PowerShell in which I instantiate Junksoft's COM API as a dynamic object that I then use:
//I'm omitting some exception handling and maintenance code for brevity
powerShell = System.Management.Automation.PowerShell.Create();
powerShell.AddScript("Add-Type -Path C:\Path\To\Junksoft\Scripting.dll");
powerShell.AddScript("New-Object Com.Junksoft.Scripting.ScriptingObject");
dynamic junksoftAPI = powerShell.Invoke()[0];
//Now we issue commands to junksoftAPI like this:
junksoftAPI.Login(user,pass);
int age = junksoftAPI.GetAgeByCustomerId(custId);
List<string> names = junksoftAPI.GetNames();
This works fine when I run all of this on the same thread (e.g. in a console application). However, for some reason this usually doesn't work when I put junksoftAPI into a System.Web.Caching.Cache and use it from different controllers in my web app. I say ususally because this actually works when ASP.NET happens to give the incoming call to the thread that junksoftAPI was created on. If it doesn't, Junksoft 95 gives me an error.
Is there any way for me to make sure that all interactions with junksoftAPI happen on the same thread?
Note that I don't want to turn the whole web application into a single-threaded application! The logic in the controllers and elswhere should happen like normal on different threads. It should only be the Junksoft interactions that happen on the Junksoft-specific thread, something like this:
[HttpGet]
public IHttpActionResult GetAge(...)
{
//finding customer ID in database...
...
int custAge = await Task.Run(() => {
//this should happen on the Junksoft-specific thread and not the next available thread
var cache = new System.Web.Caching.Cache();
var junksoftAPI = cache.Get(...); //This has previously been added to cache on the Junksoft-specific thread
return junksoftAPI.GetAgeByCustomerId(custId);
});
//prepare a response using custAge...
}
You can create your own singleton worker thread to achieve this. Here is the code which you can plug it into your web application.
public class JunkSoftRunner
{
private static JunkSoftRunner _instance;
//singleton pattern to restrict all the actions to be executed on a single thread only.
public static JunkSoftRunner Instance => _instance ?? (_instance = new JunkSoftRunner());
private readonly SemaphoreSlim _semaphore;
private readonly AutoResetEvent _newTaskRunSignal;
private TaskCompletionSource<object> _taskCompletionSource;
private Func<object> _func;
private JunkSoftRunner()
{
_semaphore = new SemaphoreSlim(1, 1);
_newTaskRunSignal = new AutoResetEvent(false);
var contextThread = new Thread(ThreadLooper)
{
Priority = ThreadPriority.Highest
};
contextThread.Start();
}
private void ThreadLooper()
{
while (true)
{
//wait till the next task signal is received.
_newTaskRunSignal.WaitOne();
//next task execution signal is received.
try
{
//try execute the task and get the result
var result = _func.Invoke();
//task executed successfully, set the result
_taskCompletionSource.SetResult(result);
}
catch (Exception ex)
{
//task execution threw an exception, set the exception and continue with the looper
_taskCompletionSource.SetException(ex);
}
}
}
public async Task<TResult> Run<TResult>(Func<TResult> func, CancellationToken cancellationToken = default(CancellationToken))
{
//allows only one thread to run at a time.
await _semaphore.WaitAsync(cancellationToken);
//thread has acquired the semaphore and entered
try
{
//create new task completion source to wait for func to get executed on the context thread
_taskCompletionSource = new TaskCompletionSource<object>();
//set the function to be executed by the context thread
_func = () => func();
//signal the waiting context thread that it is time to execute the task
_newTaskRunSignal.Set();
//wait and return the result till the task execution is finished on the context/looper thread.
return (TResult)await _taskCompletionSource.Task;
}
finally
{
//release the semaphore to allow other threads to acquire it.
_semaphore.Release();
}
}
}
Console Main Method for testing:
public class Program
{
//testing the junk soft runner
public static void Main()
{
//get the singleton instance
var softRunner = JunkSoftRunner.Instance;
//simulate web request on different threads
for (var i = 0; i < 10; i++)
{
var taskIndex = i;
//launch a web request on a new thread.
Task.Run(async () =>
{
Console.WriteLine($"Task{taskIndex} (ThreadID:'{Thread.CurrentThread.ManagedThreadId})' Launched");
return await softRunner.Run(() =>
{
Console.WriteLine($"->Task{taskIndex} Completed On '{Thread.CurrentThread.ManagedThreadId}' thread.");
return taskIndex;
});
});
}
}
}
Output:
Notice that, though the function was launched from the different threads, some portion of code got always executed always on the same context thread with ID: '5'.
But beware that, though all the web requests are executed on independent threads, they will eventually wait for some tasks to get executed on the singleton worker thread. This will eventually create a bottle neck in your web application. This is anyway your design limitation.
Here is how you could issue commands to the Junksoft API from a dedicated STA thread, using a BlockingCollection class:
public class JunksoftSTA : IDisposable
{
private readonly BlockingCollection<Action<Lazy<dynamic>>> _pump;
private readonly Thread _thread;
public JunksoftSTA()
{
_pump = new BlockingCollection<Action<Lazy<dynamic>>>();
_thread = new Thread(() =>
{
var lazyApi = new Lazy<dynamic>(() =>
{
var powerShell = System.Management.Automation.PowerShell.Create();
powerShell.AddScript("Add-Type -Path C:\Path\To\Junksoft.dll");
powerShell.AddScript("New-Object Com.Junksoft.ScriptingObject");
dynamic junksoftAPI = powerShell.Invoke()[0];
return junksoftAPI;
});
foreach (var action in _pump.GetConsumingEnumerable())
{
action(lazyApi);
}
});
_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start();
}
public Task<T> CallAsync<T>(Func<dynamic, T> function)
{
var tcs = new TaskCompletionSource<T>(
TaskCreationOptions.RunContinuationsAsynchronously);
_pump.Add(lazyApi =>
{
try
{
var result = function(lazyApi.Value);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
return tcs.Task;
}
public Task CallAsync(Action<dynamic> action)
{
return CallAsync<object>(api => { action(api); return null; });
}
public void Dispose() => _pump.CompleteAdding();
public void Join() => _thread.Join();
}
The purpose of using the Lazy class is for surfacing a possible exception during the construction of the dynamic object, by propagating it to the callers.
...exceptions are cached. That is, if the factory method throws an exception the first time a thread tries to access the Value property of the Lazy<T> object, the same exception is thrown on every subsequent attempt.
Usage example:
// A static field stored somewhere
public static readonly JunksoftSTA JunksoftStatic = new JunksoftSTA();
await JunksoftStatic.CallAsync(api => { api.Login("x", "y"); });
int age = await JunksoftStatic.CallAsync(api => api.GetAgeByCustomerId(custId));
In case you find that a single STA thread is not enough to serve all the requests in a timely manner, you could add more STA threads, all of them running the same code (private readonly Thread[] _threads; etc). The BlockingCollection class is thread-safe and can be consumed concurrently by any number of threads.
If you did not say that was a 3rd party tool, I would have asumed it is a GUI class. For practical reasons, it is a very bad idea to have multiple threads write to them. .NET enforces a strict "only the creating thread shall write" rule, from 2.0 onward.
WebServers in general and ASP.Net in particular use a pretty big thread pool. We are talking 10's to 100's of Threads per Core. That means it is really hard to nail any request down to a specific Thread. You might as well not try.
Again, looking at the GUI classes might be your best bet. You could basically make a single thread with the sole purpose of immitating a GUI's Event Queue. The Main/UI Thread of your average Windows Forms application, is responsible for creating every GUI class instance. It is kept alive by polling/processing the event queue. It ends onlyx when it receies a cancel command, via teh Event Queue. Dispatching just puts orders into that Queue, so we can avoid Cross-Threading issues.
I have a method which wraps a callback using TaskCompletionSource like this:
public Task<int> TestMethod(int argument)
{
var tcs = new TaskCompletionSource<int>();
var task = tcs.Task;
LegacyMethodWithCallback(argument, (returnValue) => tcs.TrySetResult(returnValue));
return task;
}
And then I await on that method:
async int CallingMethod()
{
var returnValue = await TestMethod(5);
Console.WriteLine(returnValue);
return returnValue;
}
EDIT: LegacyMethodWithCallback is a method communicating with a server using the network. The callback runs on a thread belonging to our proprietary thread pool (which is a reason to implement a custom SynchronizationContext). The custom SynchronizationContext is basically blank except its Post method enqueues the delegate into out thread pool. Here is implementation of the SC that should demonstrate when Post is called:
internal class ServiceSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine("Post " + d + ": " + state + " for " + this.serviceLogic.ServiceLogicId);
// Enqueue the delegate into our thread pool
}
}
I have implemented my own synchronization context, but I was suprised to see that CallingMethod appears to resume immediately on the same thread which called the TrySetResult (and therefore the same thread which invoked the callback). I originally expected the continuation to be Post-ed via the SynchronizationContext.
Is this behavior guaranteed or are there any cases in which the SynchronizationContext (or any other thread) is invoked? If this behavior is not fully deterministic, can I control it somehow?
I thoroughly inspected the call stack at the moment when CallingMethod resumes and it appears that the SynchronizationContext is not even considered (but there is still some code that cannot be easily looked into), so it appears that this behavior is inded guaranteed. Am I correct?
Is this behavior guaranteed or are there any cases in which the SynchronizationContext (or any other thread) is invoked?
SynchronizationContext, if present, is always used by the awaiting code (unless specified otherwise by ConfigureAwait(false)). That means you can't assume the awaiting code will run on your thread if you can't control the awaiting code.
There are a few reasons for you continuations not to be inlined on the same thread. One is whether TaskCreationOptions.RunContinuationsAsynchronously is used. Others like the current SynchronizationContext and TaskScheduler are in IsValidLocationForInlining:
internal static bool IsValidLocationForInlining
{
get
{
// If there's a SynchronizationContext, we'll be conservative and say
// this is a bad location to inline.
var ctx = SynchronizationContext.CurrentNoFlow;
if (ctx != null && ctx.GetType() != typeof(SynchronizationContext)) return false;
// Similarly, if there's a non-default TaskScheduler, we'll be conservative
// and say this is a bad location to inline.
var sched = TaskScheduler.InternalCurrent;
return sched == null || sched == TaskScheduler.Default;
}
}
I noticed an unexpected (and I'd say, a redundant) thread switch after await inside asynchronous ASP.NET Web API controller method.
For example, below I'd expect to see the same ManagedThreadId at locations #2 and 3#, but most often I see a different thread at #3:
public class TestController : ApiController
{
public async Task<string> GetData()
{
Debug.WriteLine(new
{
where = "1) before await",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
await Task.Delay(100).ContinueWith(t =>
{
Debug.WriteLine(new
{
where = "2) inside ContinueWith",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
}, TaskContinuationOptions.ExecuteSynchronously); //.ConfigureAwait(false);
Debug.WriteLine(new
{
where = "3) after await",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
return "OK";
}
}
I've looked at the implementation of AspNetSynchronizationContext.Post, essentially it comes down to this:
Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask;
Thus, the continuation is scheduled on ThreadPool, rather than gets inlined. Here, ContinueWith uses TaskScheduler.Current, which in my experience is always an instance of ThreadPoolTaskScheduler inside ASP.NET (but it doesn't have to be that, see below).
I could eliminate a redundant thread switch like this with ConfigureAwait(false) or a custom awaiter, but that would take away the automatic flow of the HTTP request's state properties like HttpContext.Current.
There's another side effect of the current implementation of AspNetSynchronizationContext.Post. It results in a deadlock in the following case:
await Task.Factory.StartNew(
async () =>
{
return await Task.Factory.StartNew(
() => Type.Missing,
CancellationToken.None,
TaskCreationOptions.None,
scheduler: TaskScheduler.FromCurrentSynchronizationContext());
},
CancellationToken.None,
TaskCreationOptions.None,
scheduler: TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
This example, albeit a bit contrived, shows what may happen if TaskScheduler.Current is TaskScheduler.FromCurrentSynchronizationContext(), i.e., made from AspNetSynchronizationContext. It doesn't use any blocking code and would have been executed smoothly in WinForms or WPF.
This behavior of AspNetSynchronizationContext is different from the v4.0 implementation (which is still there as LegacyAspNetSynchronizationContext).
So, what is the reason for such change? I thought, the idea behind this might be to reduce the gap for deadlocks, but deadlock are still possible with the current implementation, when using Task.Wait() or Task.Result.
IMO, it'd more appropriate to put it like this:
Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action),
TaskContinuationOptions.ExecuteSynchronously);
_lastScheduledTask = newTask;
Or, at least, I'd expect it to use TaskScheduler.Default rather than TaskScheduler.Current.
If I enable LegacyAspNetSynchronizationContext with <add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" /> in web.config, it works as desired: the synchronization context gets installed on the thread where the awaited task has ended, and the continuation is synchronously executed there.
That the continuation is being dispatched onto a new thread rather than inlined is intentional. Let's break this down:
You're calling Task.Delay(100). After 100 milliseconds, the underlying Task will transition to a completed state. But that transition will happen on an arbitrary ThreadPool / IOCP thread; it won't happen on a thread under the ASP.NET sync context.
The .ContinueWith(..., ExecuteSynchronously) will cause the Debug.WriteLine(2) to take place on the thread that transitioned the Task.Delay(100) to a terminal state. ContinueWith will itself return a new Task.
You're awaiting the Task returned by [2]. Since the thread which completes Task [2] isn't under the control of the ASP.NET sync context, the async / await machinery will call SynchronizationContext.Post. This method is contracted always to dispatch asynchronously.
The async / await machinery does have some optimizations to execute continuations inline on the completing thread rather than calling SynchronizationContext.Post, but that optimization only kicks in if the completing thread is currently running under the sync context that it's about to dispatch to. This isn't the case in your sample above, as [2] is running on an arbitrary thread pool thread, but it needs to dispatch back to the AspNetSynchronizationContext to run the [3] continuation. This also explains why the thread hop doesn't occur if you use .ConfigureAwait(false): the [3] continuation can be inlined in [2] since it's going to be dispatched under the default sync context.
To your other questions re: Task.Wait() and Task.Result, the new sync context was not intended to reduce deadlock conditions relative to .NET 4.0. (In fact, it's slightly easier to get deadlocks in the new sync context than it was in the old context.) The new sync context was intended to have an implementation of .Post() that plays well with the async / await machinery, which the old sync context failed miserably at doing. (The old sync context's implementation of .Post() was to block the calling thread until the synchronization primitive was available, then dispatch the callback inline.)
Calling Task.Wait() and Task.Result from the request thread on a Task not known to be completed can still cause deadlocks, just like calling Task.Wait() or Task.Result from the UI thread in a Win Forms or WPF application.
Finally, the weirdness with Task.Factory.StartNew might be an actual bug. But until there's an actual (non-contrived) scenario to support this, the team would not be inclined to investigate this further.
Now my guess is, they have implemented AspNetSynchronizationContext.Post this way to avoid a possibility of infinite recursion which might lead to stack overflow. That might happen if Post is called from the callback passed to Post itself.
Still, I think an extra thread switch might be too expensive for this. It could have been possibly avoided like this:
var sameStackFrame = true
try
{
//TODO: also use TaskScheduler.Default rather than TaskScheduler.Current
Task newTask = _lastScheduledTask.ContinueWith(completedTask =>
{
if (sameStackFrame) // avoid potential recursion
return completedTask.ContinueWith(_ => SafeWrapCallback(action));
else
{
SafeWrapCallback(action);
return completedTask;
}
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap();
_lastScheduledTask = newTask;
}
finally
{
sameStackFrame = false;
}
Based on this idea, I've created a custom awaiter which gives me the desired behavior:
await task.ConfigureContinue(synchronously: true);
It uses SynchronizationContext.Post if operation completed synchronously on the same stack frame, and SynchronizationContext.Send if it did on a different stack frame (it could even be the same thread, asynchronously reused by ThreadPool after some cycles):
using System;
using System.Diagnostics;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace TestApp.Controllers
{
/// <summary>
/// TestController
/// </summary>
public class TestController : ApiController
{
public async Task<string> GetData()
{
Debug.WriteLine(String.Empty);
Debug.WriteLine(new
{
where = "before await",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
// add some state to flow
HttpContext.Current.Items.Add("_context_key", "_contextValue");
CallContext.LogicalSetData("_key", "_value");
var task = Task.Delay(100).ContinueWith(t =>
{
Debug.WriteLine(new
{
where = "inside ContinueWith",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
// return something as we only have the generic awaiter so far
return Type.Missing;
}, TaskContinuationOptions.ExecuteSynchronously);
await task.ConfigureContinue(synchronously: true);
Debug.WriteLine(new
{
logicalData = CallContext.LogicalGetData("_key"),
contextData = HttpContext.Current.Items["_context_key"],
where = "after await",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
return "OK";
}
}
/// <summary>
/// TaskExt
/// </summary>
public static class TaskExt
{
/// <summary>
/// ConfigureContinue - http://stackoverflow.com/q/23062154/1768303
/// </summary>
public static ContextAwaiter<TResult> ConfigureContinue<TResult>(this Task<TResult> #this, bool synchronously = true)
{
return new ContextAwaiter<TResult>(#this, synchronously);
}
/// <summary>
/// ContextAwaiter
/// TODO: non-generic version
/// </summary>
public class ContextAwaiter<TResult> :
System.Runtime.CompilerServices.ICriticalNotifyCompletion
{
readonly bool _synchronously;
readonly Task<TResult> _task;
public ContextAwaiter(Task<TResult> task, bool synchronously)
{
_task = task;
_synchronously = synchronously;
}
// awaiter methods
public ContextAwaiter<TResult> GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return _task.IsCompleted; }
}
public TResult GetResult()
{
return _task.Result;
}
// ICriticalNotifyCompletion
public void OnCompleted(Action continuation)
{
UnsafeOnCompleted(continuation);
}
// Why UnsafeOnCompleted? http://blogs.msdn.com/b/pfxteam/archive/2012/02/29/10274035.aspx
public void UnsafeOnCompleted(Action continuation)
{
var syncContext = SynchronizationContext.Current;
var sameStackFrame = true;
try
{
_task.ContinueWith(_ =>
{
if (null != syncContext)
{
// async if the same stack frame
if (sameStackFrame)
syncContext.Post(__ => continuation(), null);
else
syncContext.Send(__ => continuation(), null);
}
else
{
continuation();
}
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
finally
{
sameStackFrame = false;
}
}
}
}
}
I've been away from .NET desktop programming for some time, while drinking the Node.js koolaid. There are some parts of Node.js I find easy to work with. In particular, I like the simplicity of the threading model, and that I can have a few of the benefits of a multithreaded application while only writing code to keep track of a single thread.
Now, I have a need to write a multi-threaded application in .NET, and it occurred to me that there is no reason I cannot use a similar threading model that is used to build Node.js applications. In particular, I want to:
Call long-running functions with callback parameters. (That function would execute on a thread from a pool. Maybe a simple wrapper function to call functions on new threads would be sufficient?)
Have those callback function calls ran on the "main" thread for processing
Maintain automatic synchronization for all objects accessed by this "main" thread, so locking isn't an issue
Does such a framework for this threading model already exist within, or for .NET applications? If not, are there parts of .NET that already support or handle some of the functionality that I am seeking?
As others have mentioned, async / await is an excellent choice for .NET. In particular:
Task / Task<T> / TaskCompletionSource<T> are analogous to JavaScript's Deferred / Promise / Future.
It's pretty easy to create JavaScript-style continuations using .NET-style continuations, but for the most part you won't need them.
There is no JavaScript equivalent to async / await. async allows you to write your methods as though they were synchronous, and under the hood it breaks them up into continuations wherever there's an await. So you don't have to use continuation passing style.
For operations on a background thread, your best choice is Task.Run. However, the standard pattern for .NET is to have the background operation compute and return a single value, instead of having continuous bidirectional messaging with the main thread.
If you do need a "stream" of asynchronous data, you should use TPL Dataflow or Rx. This is where things diverge from JS quite a bit.
I recommend you start with my async / await intro post.
I would recommend the TPL. Here’s an example of how it works
Void Work()
{
Task<string> ts = Get();
ts.ContinueWith(t =>
{
string result = t.Result;
Console.WriteLine(result);
});
}
There are a whole range of possibilities for cancelation, error handling using different schedulers etc. With .Net 4.5 you have the possibility of using await
async void Work()
{
Task<string> ts = Get();
string result = await ts;
Console.WriteLine(result);
}
Here the compiler looks at methods marked async and adds a whole pile of thread safe robust task synchronizing code while leaving the code readable.
I recommend a look at TPL (Task Parallel Library) which became available in .Net 4.0. It can do points 1 and 2 but not 3.
See http://msdn.microsoft.com/en-us/library/dd460717.aspx
It can be achieved, among other options, by taking advantage of Window's native event loop.
Following code is a POC for the same and it addresses all the 3 points you have mentioned.
But note that it is just a POC. It is not type safe and it uses Delegate.DynamicInvoke which can be slow but it proves the concept nevertheless.
public static class EventLoop
{
private class EventTask
{
public EventTask(Delegate taskHandler) : this(taskHandler, null) {}
public EventTask(Delegate taskHandler, Delegate callback)
{
TaskHandler = taskHandler;
Callback = callback;
}
private Delegate Callback {get; set;}
private Delegate TaskHandler {get; set;}
public void Invoke(object param)
{
object[] paramArr = null;
if (param.GetType().Equals(typeof(object[])))
{
paramArr = (object[]) param; //So that DynamicInvoke does not complain
}
object res = null;
if (TaskHandler != null)
{
if (paramArr != null)
{
res = TaskHandler.DynamicInvoke(paramArr);
}
else
{
res = TaskHandler.DynamicInvoke(param);
}
}
if (Callback != null)
{
EnqueueSyncTask(Callback, res);
}
}
}
private static WindowsFormsSynchronizationContext _syncContext;
public static void Run(Action<string[]> mainProc, string[] args)
{
//You need to reference System.Windows.Forms
_syncContext = new WindowsFormsSynchronizationContext();
EnqueueSyncTask(mainProc, args);
Application.Run();
}
public static void EnqueueSyncTask(Delegate taskHandler, object param)
{
//All these tasks will run one-by-one in order on Main thread
//either on call of Application.DoEvenets or when Main thread becomes idle
_syncContext.Post(new EventTask(taskHandler).Invoke, param);
}
public static void EnqueueAsyncTask(Delegate taskHandler, object param, Delegate callback)
{
//En-queue on .Net Thread Pool
ThreadPool.QueueUserWorkItem(new EventTask(taskHandler, callback).Invoke, param);
}
}
Client Code:
[STAThread]
static void Main(string[] args)
{
Thread.CurrentThread.Name = "MAIN THREAD";
Console.WriteLine("Method Main: " + Thread.CurrentThread.Name);
EventLoop.Run(MainProc, args);
}
static void MainProc(string[] args)
{
Console.WriteLine("Method MainProc: " + Thread.CurrentThread.Name);
Console.WriteLine("Queuing Long Running Task...");
EventLoop.EnqueueAsyncTask(new Func<int,int,int>(LongCalculation), new object[]{5,6}, new Action<int>(PrintResult));
Console.WriteLine("Queued Long Running Task");
Thread.Sleep(400); //Do more work
EventLoop.EnqueueAsyncTask(new Func<int, int, int>(LongCalculation), new object[] { 15, 16 }, new Action<int>(PrintResult));
Thread.Sleep(150); //Do some more work but within this time 2nd task is not able to complete, meanwhile 1st task completes
//Long running Tasks will run in background but callback will be executed only when Main thread becomes idle
//To execute the callbacks before that, call Application.DoEvents
Application.DoEvents(); //PrintResult for 1st task as 2nd is not yet complete
Console.WriteLine("Method MainProc: Working over-time!!!!");
Thread.Sleep(500); //After this sleep, 2nd Task's print will also be called as Main thread will become idle
}
static int LongCalculation(int a, int b)
{
Console.WriteLine("Method LongCalculation, Is Thread Pool Thread: " + Thread.CurrentThread.IsThreadPoolThread);
Console.WriteLine("Running Long Calculation");
Thread.Sleep(500); //long calc
Console.WriteLine("completed Long Calculation");
return a + b;
}
static void PrintResult(int a)
{
Console.WriteLine("Method PrintResult: " + Thread.CurrentThread.Name);
Console.WriteLine("Result: " + a);
//Continue processing potentially queuing more long running tasks
}
Output:
I'd like to use Task return from a method to return a the value when it becomes available at later time, so that the caller can either block using Wait or attach a continuation or even await it. The best I can think of is this:
public class Future<T> {
private ManualResetEvent mre = new ManualResetEvent();
private T value;
public async Task<T> GetTask() {
mre.WaitOne();
return value;
}
public void Return(T value) {
this.value = value;
mre.Set();
}
}
Main problem with that is that mre.WaitOne() is blocking, so i assume that every call to GetTask() will schedule a new thread to block. Is there a way to await a WaitHandle in an async manner or is there already a helper for building the equivalent functionality?
Edit: Ok, is TaskCompletionSource what i'm looking for and i'm just making life hard on myself?
Well, I guess I should have dug around a bit more before posting. TaskCompletionSource is exactly what I was looking for
var tcs = new TaskCompletionSource<int>();
bool completed = false;
var tc = tcs.Task.ContinueWith(t => completed = t.IsCompleted);
tcs.SetResult(1);
tc.Wait();
Assert.IsTrue(completed);
Blocking a thread is bad by calling the WaitHandle.WaitOne(), but that's how events work, and the selected answer does not make a lot of sense, because it does not do anything asynchronously.
HOWEVER, the .NET framework can utilize worker threads from a thread pool to wait on multiple events at the same time (see ThreadPool.RegisterWaitForSingleObject) - this will improve overall resource utilization in your app if you need to wait on multiple WaitHandles at the same time.
So what you can do, is to register the WaitHandle for waiting on a worker thread, and set the callback with desired continuation.
With the AsyncWaitHandle extension library (NuGet) this can be done in one line:
var mre = new ManualResetEvent();
T myValue = ...;
Task<T> futureValueTask = mre.WaitOneAsync().ContinueWith(() => myValue);
Overall, my humble suggestion is to review the code and do this instead:
async Task MyCode()
{
var mre = new ManualResetEvent();
StartDoingSmthAsynchronouslyThatPulsesTheEvent(mre);
await mre;
// finish routine here when the event fires
}
Can't you just leverage TaskEx.WhenAll(t1, t2, t3...) to do the waiting. You'd need a real task that represents the doing of the work, but if you said something like:
private Task<T> myRealWork;
private T value;
// ...
public async Task<T> GetTask()
{
value = await TaskEx.WhenAll(myRealWork);
return value;
}
Although you can probably just await the myRealWork task as well. I don't see in your code where the value is actually being computed. That might require something like:
public async Task<T> GetTask()
{
value = await TaskEx.RunEx(() => ComputeRealValueAndReturnIt());
return value;
}