How can I prevent synchronous continuations on a Task?
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();
}
Configuring the continuation behaviour of a TaskCompletionSource's Task
There is no way to prevent synchronous task continuations. Normally, this is not a problem.
However, there are some situations where you do need this, e.g., if you are completing the task while holding a lock. In those cases, you can just Task.Run
the task completion, as such:
// Set the result on a threadpool thread, so any synchronous continuations
// will execute in the background.
Task.Run(() => tcs.TrySetResult(result));
// Wait for the TCS task to complete; note that the continuations
// may not be complete.
tcs.Task.Wait();
This is an advanced technique. It is an exception to the guideline "Don't block on async
code (async
all the way down)" as expounded on my blog.
It's part of my AsyncEx library, as an extension method:
public static void TrySetResultWithBackgroundContinuations<TResult>(
this TaskCompletionSource<TResult> @this, TResult result);
This technique was first published by Stephen Toub on his blog.
Prevent a Task Continuation
From MSDN
a user-defined value can be passed from the antecedent to its
continuation in the Result property, so that the output of the
antecedent can serve as input for the continuation
So one way would be in task3, check the Result from task2 before doing any work.
var task2 = task1.ContinueWith(result => DoSomeMoreWOrk(), TaskContinuationOptions.OnlyOnRanToCompletion);
var task3 = task2.ContinueWith(x => DoFinalWork(x.Result));
Where the Result returned from task2 determines what happens in task3.
EDIT
Another solution would be to cancel task2 if certain conditions are not met within the operation. Then the continuation task is not even scheduled.
From MSDN
To prevent a continuation from executing if its antecedent is
canceled, specify the NotOnCanceled option when you create the
continuation.
So task3 definition becomes
var task3 = task2.ContinueWith(result => DoFinalWork(), TaskContinuationOptions.NotOnCancelled);
C# How to prevent Task from conflicting with running Task?
One option would be to use a limited concurrency task scheduler (see example). This can ensure that each task is run sequentially.
Another option is to start a thread that waits on a autoResetEvent in a loop. The timer would simply trigger the autoResetEvent, releasing the thread to do its job. The downside is that this would consume a whole thread.
A third option would be to set a flag when starting the work, and reset it when it is complete. The timer could then skip starting the task if the flag is set. You might want to use lock or interlocked to avoid thread safety issues.
There might be some issues when adding items just before the task completes, but if it is run on a timer I would assume all items would be processed eventually.
TaskContinuationOptions.RunContinuationsAsynchronously and Stack Dives
The key concept here is that a task's continuation may run synchronously on the same thread that completed the antecedent task.
Let's imagine that this is SemaphoreSlim.Release
's implementation (it's actually Toub's AsyncSemphore
's):
public void Release()
{
TaskCompletionSource<bool> toRelease = null;
lock (m_waiters)
{
if (m_waiters.Count > 0)
toRelease = m_waiters.Dequeue();
else
++m_currentCount;
}
if (toRelease != null)
toRelease.SetResult(true);
}
We can see that it synchronously completes a task (using TaskCompletionSource
).
In this case, if WorkAsync
has no other asynchronous points (i.e. no await
s at all, or all await
s are on an already completed task) and calling _gate.Release()
may complete a pending call to _gate.WaitAsync()
synchronously on the same thread you may reach a state in which a single thread sequentially releases the semaphore, completes the next pending call, executes // work here
and then releases the semaphore again etc. etc.
This means that the same thread goes deeper and deeper in the stack, hence stack dive.
RunContinuationsAsynchronously
makes sure the continuation doesn't run synchronously and so the thread that releases the semaphore moves on and the continuation is scheduled for another thread (which one depends on the other continuation parameters e.g. TaskScheduler
)
This logically resembles posting the completion to the ThreadPool
:
public void Release()
{
TaskCompletionSource<bool> toRelease = null;
lock (m_waiters)
{
if (m_waiters.Count > 0)
toRelease = m_waiters.Dequeue();
else
++m_currentCount;
}
if (toRelease != null)
Task.Run(() => toRelease.SetResult(true));
}
TaskContinuations ran synchronously with async lambdas
I second Marc's comment: you should avoid ContinueWith
; it's a low-level API that has potentially dangerous default behavior.
The part after the await - will it effectively release the thread that started executing the task continuation?
The await
will do that, yes.
And on which thread will the rest of this continuation resume? ... The question is asked when there is no SynchronizationContext present for the antecedent task of the continuation above
Most likely, it would be the thread pool thread that completes the Task
returned by Task.Delay
. This is because await
uses TaskContinuationOptions.ExecuteSynchronously
(note: this is an implementation detail; do not depend on this behavior).
I say "most likely" because there are situations where ExecuteSynchronously
doesn't execute synchronously. It's an optimization hint, not a requirement. This also applies to the part of your continuation before the await
: it is most likely (not definitely) running on the thread that completed the antecedent task.
but I've heard that SynchronziationContext does not flow through ContinueWith method calls? Is that true?
ContinueWith
works at the task level. It has some logic for flowing through TaskScheduler.Current
(which IMO is one aspect that makes this API dangerous), but has no awareness of SynchronizationContext
.
How can I guarantee continuations run in task completion order?
If you use TAP (Task Asynchronous Programming), i.e. async
and await
, you can make the flow of processing a lot more apparent. In this case I would create a new method to encapsulate the order of operations:
public async Task ProcessStagedOperation(StagedOperation op, int i)
{
await op.Connect;
Console.WriteLine("{0}: Connected", i);
await op.Accept;
Console.WriteLine("{0}: Accepted", i)
await op.Complete;
Console.WriteLine("{0}: Completed", i)
}
Now your processing loop gets simplified a bit:
public async Task RunOperations()
{
List<Task> pendingOperations = new List<Task>();
for (int i=0; i<3; i++)
{
var op = InitiateStagedOperation(i);
pendingOperations.Add(ProcessStagedOperation(op, i));
}
await Task.WhenAll(pendingOperations); // finish
}
You now have a reference to a task object you can explicitly wait or simply await
from another context. (or you can simply ignore it). The way I modified the RunOperations()
method allows you to create a large queue of pending tasks but not block while you wait for them all to finish.
Can I guarantee runnable task continuations have been run?
Can I guarantee runnable task continuations have been run?
By await
ing them.
var i = 0;
var taskCompletionSource = new TaskCompletionSource<object>();
var task = taskCompletionSource.Task;
var continuation = Task.Run(async () =>
{
await task;
i = 1;
});
// Synchronously complete `task`
taskCompletionSource.SetResult(null);
// wait for the continuation
await continuation;
// ouputs 1
Console.WriteLine(i);
That works if you're withing an asynchronous method. If you're not, make it asynchronous. You can technically block, too, (.Wait()
instead of await
) but that invites deadlocks, so be careful.
Related Topics
Sending Windows Key Using Sendkeys
How to Force My C# Winforms Program Run as Administrator on Any Computer
C# Wait for User to Finish Typing in a Text Box
How to Optionally Turn Off the JSONignore Attribute at Runtime
Right Way to Dispose Image/Bitmap and Picturebox
Does Using Parameterized SQLcommand Make My Program Immune to SQL Injection
Working with Incredibly Large Numbers in .Net
How to Send a File and Form Data with Httpclient in C#
How to Set the Windows Default Printer in C#
How to Turn Off Impersonation Just in a Couple Instances
Can the Oracle Managed Driver Use Async/Await Properly
Parent Control Mouse Enter/Leave Events with Child Controls
How to Read/Stream a File Without Loading the Entire File into Memory
How to Embed Multiple Images in Email Body Using .Net