I was reading about SynchronizationContext and its use with the async/await methods (link). From my understanding, in a Console application where the SynchronizationContext is null, the continuation of an awaited method (Task) will be scheduled with the default scheduler which would be the ThreadPool.
But if I run this console app, you'll see from the output that the the continuation is run on the worker thread that I created:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("MainThreadId=" + Thread.CurrentThread.ManagedThreadId);
Method1().ContinueWith(t =>
{
Console.WriteLine("After Method1. ThreadId=" + Thread.CurrentThread.ManagedThreadId);
});
Console.ReadKey();
}
public static async Task Method1()
{
Console.WriteLine("Method1 => Entered. ThreadId=" + Thread.CurrentThread.ManagedThreadId);
TaskCompletionSource<bool> completionSource = new TaskCompletionSource<bool>();
Thread thread = new Thread(() =>
{
Console.WriteLine("Method1 => Started new thread. ThreadId=" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
completionSource.SetResult(true);
});
thread.Start();
await completionSource.Task;
Console.WriteLine("Method1 => After WorkerThread. ThreadId=" + Thread.CurrentThread.ManagedThreadId);
}
}
And here is the output:
MainThreadId=10
Method1 => Entered. ThreadId=10
Method1 => Started new thread. ThreadId=11
Method1 => After WorkerThread. ThreadId=11
After Method1. ThreadId=12
As you can see, "After WorkerThread" was outputted on the same thread as my workerthread, but not on the threadpool.
I found a similar question but the guy was using Mono and they were saying that this was a bug. On my side, I built this code with in Visual Studio and ran it under Windows 7 and .Net 4.5.2 installed on my machine.
Could someone please explain this behaviour?
It's because of an implementation detail that I documented on my blog: the continuation created by await is scheduled using the ExecuteSynchronously flag. In that case, when it is time to fire the continuation (i.e., in the TaskCompletionSource<T>.SetResult call on the worker thread), the default scheduler first attempts to determine whether it can run on the current thread.
Since the worker thread has no TaskScheduler that will reject executing the task synchronously, the ExecuteSynchronously flag will cause the thread pool task scheduler to just execute the task synchronously (i.e., on the calling thread).
Related
I know there are a million questions on this and I am trying their solutions but it does not seem to behave as I would expect.
With the following code:
await Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
Console.WriteLine("I'm running after Task.Run");
I get the output:
[ 5 seconds elapses ]
I'm running after Thread.Sleep
I'm running after Task.Run
Which is the same output I would get without using Task.Run
What I want is the output:
I'm running after Task.Run
[ 5 seconds elapses ]
I'm running after Thread.Sleep
Where execution of the 'parent' thread continues and the long-running task is executed 'in the background' in a way which does not affect the 'parent' thread.
As mentioned you need to remove the await. However that turns it into a fire-and-forget task which, if the above code just happens to be in say a console app's Main then your process could terminate before the task has completed.
Good:
// C# 7
static async void Main(string[] args) // <--- note `async` signature
{
await Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running
after Thread.Sleep"); });
Console.WriteLine("I'm running after Task.Run");
}
Bad: - fire-and-forget task that isn't awaited
static void Main(string[] args)
{
Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("Spider Man: I don't wan't to go!"); }); // POOF!!
Console.WriteLine("I'm running after Task.Run");
// sorry Spidy, you're dead
}
So you might not see I'm running after Thread.Sleep at all even if you have a Thread.Sleep(). (by the way you should be using Task.Delay()). You could mitigate it with a Console.ReadKey() but that might defeat the purpose.
Fire-and-forget tasks have their uses, and can be used reasonably safely in other types of long-running apps like say WinForms but people generally don't go spawning Tasks in the process's entry point so the tasks generally run to completion without any early termination by the process. However, it's generally a good idea to use await where you can so you don't shoot yourself in the foot.
Also, your requirements of wanting things in a certain order sort of demonstrates a misunderstanding of how async/await works.
One thing to keep in mind when working with await is that your code awaits objects. These objects are just like any other objects. Specifically, they can be assigned to local variables or class variables.
So, this code:
await Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
Console.WriteLine("I'm running after Task.Run");
is essentially the same as this code:
var task = Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
await task;
Console.WriteLine("I'm running after Task.Run");
In other words:
Start a task running on a background thread.
Asynchronously wait (await) for that task to complete.
Display "I'm running after Task.Run".
So the output is expected.
What I want is the output:
I'm running after Task.Run
[ 5 seconds elapses ]
I'm running after Thread.Sleep
In that case, start the task running on the background thread, but don't await it until later:
var task = Task.Run(() => { Thread.Sleep(5000); Console.WriteLine("I'm running after Thread.Sleep"); });
Console.WriteLine("I'm running after Task.Run");
await task;
If you want to run asynchronously you should remove the command away
static void Main(string[] args)
{
Task.Run(() => { Thread.Sleep(5000); Console.WriteLine(DateTime.Now + " => I'm running after Thread.Sleep"); });
Console.WriteLine(DateTime.Now + " => I'm running after Task.Run");
while (true)
{
Thread.Sleep(500);
}
}
Output
28/05/2019 09:49:58 => I'm running after Task.Run
28/05/2019 09:50:03 => I'm running after Thread.Sleep
I do not understand how is the control returned to the caller when using async- await, since when i execute this code, the first thread gets practically destroyed when calling task inside the awaited method, and the thread that gives the result executes all remaining code.Below i have also drawn a diagram of how i thought the execution is, but it seems it is wrong.
Assumed workflow according to "returning control to the caller":
Results
Main
public static string GetThreadId => Thread.CurrentThread.ManagedThreadId.ToString();
static async Task Main(string[] args) {
Console.WriteLine("From main before async call , Thread:" + GetThreadId);
string myresult = await TestAsyncSimple();
Console.WriteLine("From main after async call ,Thread:" + GetThreadId);
Console.WriteLine("ResultComputed:" + myresult+",Thread:"+GetThreadId);
Console.ReadKey();
}
Async Task
public static async Task<string> TestAsyncSimple() {
Console.WriteLine("From TestAsyncSimple before delay,Thread:" + GetThreadId);
string result=await Task.Factory.StartNew(() => {
Task.Delay(5000);
Console.WriteLine("From TestAsyncSimple inside Task,Thread:" + GetThreadId);
return "tadaa";
});
Console.WriteLine("From TestAsyncSimple after delay,Thread:" + GetThreadId);
return result;
}
Can anyone point me to the right direction?Also what causes the new thread to get spawned?Always when starting a Task ?Are there other "triggers" besides tasks that create new threads which will execute the remaining code?
async Main method is converted to something like this:
static void Main() {
RealMain().GetAwaiter().GetResult();
}
static async Task RealMain() {
// code from async Main
}
With that in mind, at "From main before async call" point you are on main application thread (id 1). This is regular (non thread pool) thread. You will be on this thread until
await Task.Factory.StartNew(...)
At this point, StartNew starts a new task which will run on a thread pool thread, which is created or grabbed from pool if already available. This is thread 3 in your example.
When you reach await - control is returned back to the caller, where caller in this case is thread 1. What this thread does after await is reched? It's blocked here:
RealMain().GetAwaiter().GetResult();
waiting for result of RealMain.
Now thread 3 has finished execution but TestAsyncSimple() has more code to run. If there were no synchronization context before await (the case here - in console application) - the part after await will be executed on available thread pool thread. Since thread 3 has finished execution of its task - it is available and is capable to continue execution of the rest of TestAsyncSimple() and Main() functions, which it does. Thread 1 all this time is blocked as said above - so it cannot process any continuations (it's busy). In addition it's also not a thread pool thread (but that is not relevent here).
After you reached Console.ReadKey and pressed a key - Main task finally completes, thread 1 (waiting for this task to complete) is unblocked, then it returns from real Main function and process is terminated (only at this point thread 1 is "destroyed").
I'm having troubles with the following example:
public void Method()
{
LongRunningMethod();
}
LongRunningMethod() takes around 5 seconds to invoke. I am invoking Method() from the UI thread, so it obviously should freeze the UI. The solution for that is to run Method() within a new Task so I am running it like this:
Task.Factory.StartNew(()=>{Method()})
It's still blocking the UI so I thought whether LongRunningMethod() is using the UI context probably. Then I tried another solution:
new Thread(()=>Method()).Start()
and it started working. How is that possible? I know that Task is not guaranteed to be run on a different thread but CLR should be smart enough to figure out that it's long running method.
You are scheduling work on the User Interface (UI) Thread cause you are using
TaskScheduler.FromCurrentSynchronizationContext()) in this code:
Task nextTask = task.ContinueWith(x =>
{
DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
}
And this is a reason why your UI is frozen. To prevent try to change TaskScheduler to Default:
Task task = Task.Run(() => {; });
Task nextTask = task.ContinueWith(x =>
{
//DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
Task.Factory.StartNew is dangerous cause it uses TaskScheduler.Current as opposed to TaskScheduler.Default. To prevent this use Task.Run which always points to TaskScheduler.Default. Task.Run is new in .NET 4.5, if you're in .NET 4.0 you can create your TaskFactory with default parameters.
As MSDN says:
TaskScheduler.FromCurrentSynchronizationContext()) means schedule
a task on the same thread that the user interface (UI) control was
created on.
Update:
What happens when you run method RunTask():
var task = new Task(action, cancellationTokenSource.Token);
create a "task". (task is not run. The "task" is just queed to the ThreadPool.)
Task nextTask = task.ContinueWith(x =>
{
DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
create a "nextTask" which will start performing AFTER "task" is completed and the "nextTask" will be performed on UI thread as you've set a feature
TaskScheduler.FromCurrentSynchronizationContext().
task.Start();
You run your "task". When the "task" is completed, then "nextTask" is run by method "task.ContinuuWith()" which will be performed on UI thread you wrote (TaskScheduler.FromCurrentSynchronizationContext()
So to sum up, the two your tasks are interconnected and continuation of task is performed on UI thread which is a reason to freeze your UI. To prevent this behavior use TaskScheduler.Default.
This is exactly how it looks like:
public void RunTask(Action action){
var task = new Task(action, cancellationTokenSource.Token);
Task nextTask = task.ContinueWith(x =>
{
DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
}
public void DoSomething()
{
if(condition) // condition is true in this case (it's recurency but not permanent)
RunTask(() => Method()); // method is being passed which blocks UI when invoked in RunTask method
}
public void Method()
{
LongRunningMethod();
}
This is the starting point invocation (UI Thread):
RunTask(()=>Action());
Only a guess: Thread.Start creates a foreground thread. Maybe the method switches to a known foreground-thread when it detects, that it is run from a background-thread.
Hope it helps somehow.
Maybe I misunderstood something, but I always think that by default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes. But I found quite strange behavior (at least for me) where this is wrong:
private static Task StartTask()
{
return Task.Run(() =>
{
Debug.WriteLine("StartTask thread id = " + Thread.CurrentThread.ManagedThreadId);
});
}
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
await Task.Run(async () =>
{
Debug.WriteLine("Thread id before await task = " + Thread.CurrentThread.ManagedThreadId);
await StartTask().ConfigureAwait(true);
Debug.WriteLine("Thread id after await task = " + Thread.CurrentThread.ManagedThreadId);
});
}
and I receive such result in debug output
Thread id before await task = 12
StartTask thread id = 13
Thread id after await task = 13
Why did code execution context change after await?
by default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes
That's correct. The behavior you observed is correct.
The question is: what is the "context" that is captured? It is the current SynchronizationContext, unless it is null, in which case it is the current TaskScheduler.
When Task.Run executes your delegate, it is executing it on the thread pool. Thus, there is no current SynchronizationContext. So the current TaskScheduler is used; note that since there is not actually a task executing (the delegate is executed directly on the thread pool), the current TaskScheduler is the default TaskScheduler, which represents the thread pool.
I am trying to use async and await commands on a form with VS2012 framework 4.5.
My async method SlowMethodAsync does not return anything. Note this code works fine in a Console application.
private void button1_Click(object sender, EventArgs e)
{
var task = SlowMethodAsync();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
System.Threading.Tasks.TaskStatus status = task.Status;
Console.WriteLine("Slow method result on Thread: {0}", task.Result); //This line never executes
Console.WriteLine("Main complete on {0}", Thread.CurrentThread.ManagedThreadId);
}
//Why is this method not returning anything?
static async Task<int> SlowMethodAsync()
{
Console.WriteLine("Slow method started on Thread: {0}", Thread.CurrentThread.ManagedThreadId);
await Task.Delay(2000);
Console.WriteLine("Slow method complete on Thread: {0}", Thread.CurrentThread.ManagedThreadId);
return 42;
}
You've caused a deadlock.
Using task.Result blocks the UI thread - it can't complete until the task returned by SlowMethodAsync completes.
However, because SlowMethodAsync was originally launched with a synchronization context also on the UI thread, the continuation after await also wants to execute on the UI thread.
So Result can't complete until the async method completes... and the async method can't complete until Result has completed.
Instead, you should make your button1_Click method async too, and then use:
Console.WriteLine("Slow method result on Thread: {0}", await task);
The reason this would have worked in a console app was that the SlowMethodAsync method wouldn't have had any particular synchronization context to come back to, so the continuation could execute on any thread pool thread - which wouldn't be blocked by the "main" thread waiting on the task.