Await operator in C# spanning a background Thread - c#

Lets assume I have the following simple program which uses the await operator in both DownloadDocsMainPageAsync() and Main(). While I understand that the current awaitable method gets suspended and continue from that point after the results are available, I need some clarity on the following points .
a) If the execution from Main() starts on Thread A from the threadpool , as soon as it encounters the await operator will this thread be returned to the threadpool for executing other operations in the program , for eg: if its a web app then for invocation of some Controller methods after button clicks from UI?
b) Will the await operator always take the execution on a new thread from the threadpool or in this case assuming there is no other method to be executed apart from Main() ,will it continue execution on the same thread itself (ThreadA)? If my understanding is correct who decides this , is it Garbage collector of CLR?
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class AwaitOperator
{
public static async Task Main()
{
Task<int> downloading = DownloadDocsMainPageAsync();
Console.WriteLine($"{nameof(Main)}: Launched downloading.");
int bytesLoaded = await downloading;
Console.WriteLine($"{nameof(Main)}: Downloaded {bytesLoaded} bytes.");
}
private static async Task<int> DownloadDocsMainPageAsync()
{
Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: About to start downloading.");
var client = new HttpClient();
byte[] content = await client.GetByteArrayAsync("https://learn.microsoft.com/en-us/");
Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: Finished downloading.");
return content.Length;
}
}

Actually, async/await is not about threads (almost), but just about control flow control. So, in your code execution goes in a Main thread (which is not from a thread pool, by the way) until reaches await client.GetByteArrayAsync. Here the real low-level downloading is internally offloaded to the OS level and the program just waits the downloading result. And still no additional thread are spawned. But, when downloading finished, the .NET runtime want to continue execution after await. And here it can see no SynchronizationContext (as a console application does not has it) and then runtime executes the code after await in any thread available in thread pool. So, the rest of code after downloading will be executed in the thread from the pool.
If you will add a SynchronizationContext (or just move the code in WinForms app where the context exists out-of-the-box) you will see that all code will be executed in the main thread on no threads will be spawned/taken in from the thread pool as the runtime will see SynchronizationContext and will schedule after-await code on the original thread.
So, the answers
a) Main starts on the Main thread, not on the thread pool's thread. await itself does not actually spawn any threads. On the await, if the current thread was from thread pool, this thread will be put back in thread pool and will be available for future work. There is an exception, when the await will continue immediately and synchronously (see below).
b) runtime decides on which thread execution will be continued after 'await' depending of the current SynchronizationContext, ConfigureAwait settings and the availability of the operation result on the moment of reaching await.
In particular
if SynchronizationContext present and ConfigureAwait is set to true (or omitted), then code always continue in the current thread.
if SynchronizationContext does not present or ConfigureAwait is set to false, code will continue in any available thread (main thread or thread pool)
if you write something like
var task = DoSomeWorkAsync();
//some synchronous work which takes a while
await task;
then you can have a situation, when task is already finished on the moment when the code reaches await. In this case runtime can continue execution after await synchronously in the same thread. But this case is implementation-specific, as I know.
additionally, this is a special class TaskCompletionSource<TResult> (docs here) which provides explicit control over the task state and, in particular, may switch execution on any thread selected by the code owning TaskCompletionSource instance (see sample in #TheodorZoulias comment or here).

Related

How can blocking async calls cause a deadlock if async calls aren't necessarily executed on a different thread?

I recently read Stephen Cleary's post regarding possible deadlocks that occur when we call async code in synchronous methods here: https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Regarding a slightly modified example here (all I added was a writeline code):
// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
Console.WriteLine("Before await");
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri).ConfigureAwait(true);
return JObject.Parse(jsonString);
}
}
// My "top-level" method.
public void Button1_Click(...)
{
var jsonTask = GetJsonAsync(...);
textBox1.Text = jsonTask.Result;
}
His explanation is that the top-level method is blocking the UI thread waiting for GetJsonAsync to finish whereas GetJsonAsync is waiting for the UI thread to be freed so it can finish executing.
My question is, isn't GetJsonAsync already on the UI thread? Why would it need to wait for it to be freed? According to this post here calling async functions don't necessarily create another thread for the method to execute on. So how would it cause an issue with the UI thread if GetJsonAsync was executing on the UI thread the entire time? Like when Console.WriteLine() is executed where is this done, if not on the UI thread? I feel like I'm missing something here but don't know what.
Clarification: At which point does the execution leave the UI thread/context and need to return? There so much discussion about needing to return but never when it leaves the thread/context.
What I'm asking is, where is GetJsonAsync executing when it's called from Button1_Click if this call doesn't create a new thread for it to execute on. Before the await within GetJsonAsync, isn't it executing the Console.WriteLine(...) within the UI context still?
I recommend reading my async intro. In summary:
Every asynchronous method begins executing synchronously. This code:
public void Button1_Click(...)
{
var jsonTask = GetJsonAsync(...);
textBox1.Text = jsonTask.Result;
}
calls GetJsonAsync on the UI thread, and it does begin executing on the UI thread. It executes Console.WriteLine on the UI thread, news up a client on the UI thread, and even calls GetStringAsync on the UI thread. It gets a task back from that method, and then awaits it (I'm ignoring the ConfigureAwait(true) for simplicity).
The await is the point at which things may become asynchronous. The task isn't complete (i.e., the client hasn't received the string yet), so GetJsonAsync returns an incomplete task to its caller. Then Button1_Click blocks the UI thread, waiting for that task to complete (by calling .Result).
So, the state is currently GetJsonAsync is no longer running on the UI thread. It is not actually "running" anywhere.
Later, when that string result arrives, the task that was returned from GetStringAsync is completed, and GetJsonAsync needs to resume executing. It's not already on the UI thread; it's not anywhere at the moment. Since the await captured a UI context, it will attempt to resume on that context (on the UI thread).

Confusions on await behavior in async Main()

I'm learning C# with Andrew Troelsen's book "Pro C# 7 With .NET and .NET Core". On chapter 19 (async programming) the author used these sample codes:
static async Task Main(string[] args)
{
Console.WriteLine(" Fun With Async ===>");
string message = await DoWorkAsync();
Console.WriteLine(message);
Console.WriteLine("Completed");
Console.ReadLine();
}
static async Task<string> DoWorkAsync()
{
return await Task.Run(() =>
{
Thread.Sleep(5_000);
return "Done with work!";
});
}
The author then states
"... this keyword (await) will always modify a method that returns a Task object. When the flow of logic reaches the await token, the calling thread is suspended in this method until the call completes. If you were to run this version of the application, you would find that the Completed message shows before the Done with work! message. If this were a graphical application, the user could continue to use the UI while the DoWorkAsync() method executes".
But when I ran this code in VS I did not get this behavior. The Main thread actually gets blocked for 5 seconds and "Completed" doesn't show until after "Done with work!".
Looking through various online documentation and articles regarding how async/await works, I thought "await" would work such as when the first "await" is encountered, the program checks if the method has already completed, and if not, it would immediately "return" to the calling method, and then come back once the awaitable task completes.
But if the calling method is Main() itself, who does it return to? Would it simply wait for the await to complete? Is that why the code is behaving as it is (waiting for 5 seconds before printing "Completed")?
But this leads to the next question: because DoWorkAsync() itself here calls another await method, when that await Task.Run() line is encountered, which would obviously not complete till 5 seconds later, shouldn't DoWorkAsync() immediately return to the calling method Main(), and if that happens, shouldn't Main() proceed to print "Completed", as the book author suggested?
BTW, the book is for C# 7 but I'm running VS 2019 with C# 8, if that makes any difference.
I strongly recommend reading this blog post from 2012 when the await keyword was introduced, but it explains how asynchronous code works in console programs: https://devblogs.microsoft.com/pfxteam/await-synchronizationcontext-and-console-apps/
The author then states
this keyword (await) will always modify a method that returns a Task object. When the flow of logic reaches the await token, the calling thread is suspended in this method until the call completes. If you were to run this version of the application, you would find that the "Completed" message shows before the "Done with work!" message. If this were a graphical application, the user could continue to use the UI while the DoWorkAsync() method executes".
The author is being imprecise.
I would change this:
When the flow of logic reaches the await token, the calling thread is suspended in this method until the call completes
To this:
When the flow of logic reaches the await token (which is after DoWorkAsync returns a Task object), the local state of the function is saved in-memory somewhere and the running thread executes a return back to the Async Scheduler (i.e. the thread pool).
My point is that await does not cause a thread to "suspend" (nor does it cause a thread to block either).
The next sentence is also a problem:
If you were to run this version of the application, you would find that the "Completed" message shows before the "Done with work!" message
(I assume by "this version" the author is referring to a version that's syntactically identical but omits the await keyword).
The claim being made is incorrect. The called method DoWorkAsync still returns a Task<String> which cannot be meaningfully passed to Console.WriteLine: the returned Task<String> must be awaited first.
Looking through various online documentation and articles regarding how async/await works, I thought "await" would work such as when the first "await" is encountered, the program checks if the method has already completed, and if not, it would immediately "return" to the calling method, and then come back once the awaitable task completes.
Your thinking is generally correct.
But if the calling method is Main() itself, who does it return to? Would it simply wait for the await to complete? Is that why the code is behaving as it is (waiting for 5 seconds before printing "Completed")?
It returns to the default Thread Pool maintained by the CLR. Every CLR program has a Thread Pool, which is why even the most trivial of .NET programs' processes will appear in Windows Task Manager with a thread-count between 4 and 10. The majority of those threads will be suspended, however (but the fact they're suspended is unrelated to the use of async/await.
But this leads to the next question: because DoWorkAsync() itself here calls another awaited method, when that await Task.Run() line is encountered, which would obviously not complete till 5 seconds later, shouldn't DoWorkAsync() immediately return to the calling method Main(), and if that happens, shouldn't Main() proceed to print "Completed", as the book author suggested?
Yes and no :)
It helps if you look at the raw CIL (MSIL) of your compiled program (await is a purely syntactic feature that does not depend on any substantial changes to the .NET CLR, which is why the async/await keywords were introduced with .NET Framework 4.5 even though the .NET Framework 4.5 runs on the same .NET 4.0 CLR which predates it by 3-4 years.
To start, I need to syntactically rearrange your program to this (this code looks different, but it compiles to identical CIL (MSIL) as your original program):
static async Task Main(string[] args)
{
Console.WriteLine(" Fun With Async ===>");
Task<String> messageTask = DoWorkAsync();
String message = await messageTask;
Console.WriteLine( message );
Console.WriteLine( "Completed" );
Console.ReadLine();
}
static async Task<string> DoWorkAsync()
{
Task<String> threadTask = Task.Run( BlockingJob );
String value = await threadTask;
return value;
}
static String BlockingJob()
{
Thread.Sleep( 5000 );
return "Done with work!";
}
Here's what happens:
The CLR loads your assembly and locates the Main entrypoint.
The CLR also populates the default thread-pool with threads it requests from the OS, it suspends those threads immediately (if the OS doesn't suspend them itself - I forget those details).
The CLR then chooses a thread to use as the Main thread and another thread as the GC thread (there's more details to this, I think it may even use the main OS-provided CLR entrypoint thread - I'm unsure of these details). We'll call this Thread0.
Thread0 then runs Console.WriteLine(" Fun With Async ===>"); as a normal method-call.
Thread0 then calls DoWorkAsync() also as a normal method-call.
Thread0 (inside DoWorkAsync) then calls Task.Run, passing a delegate (function-pointer) to BlockingJob.
Remember that Task.Run is shorthand for "schedule (not immediately-run) this delegate on a thread in the thread-pool as a conceptual "job", and immediately return a Task<T> to represent the status of that job".
For example, if the thread-pool is depleted or busy when Task.Run is called then BlockingJob won't run at all until a thread returns to the pool - or if you manually increase the size of the pool.
Thread0 is then immediately given a Task<String> that represents the lifetime and completion of BlockingJob. Note that at this point the BlockingJob method may or may not have run yet, as that's entirely up to your scheduler.
Thread0 then encounters the first await for BlockingJob's Job's Task<String>.
At this point actual CIL (MSIL) for DoWorkAsync contains an effective return statement which causes real execution to return to Main, where it then immediately returns to the thread-pool and lets the .NET async scheduler start worrying about scheduling.
This is where it gets complicated :)
So when Thread0 returns to the thread-pool, BlockingJob may or may not have been called depending on your computer setup and environment (things happen differently if your computer has only 1 CPU core, for example - but many other things too!).
It's entirely possible that Task.Run put the BlockingJob job into the scheduler and then not not actually run it until Thread0 itself returns to the thread-pool, and then the scheduler runs BlockingJob on Thread0 and the entire program only uses a single-thread.
But it's also possible that Task.Run will run BlockingJob immediately on another pool thread (and this is the likely case in this trivial program).
Now, assuming that Thread0 has yielded to the pool and Task.Run used a different thread in the thread-pool (Thread1) for BlockingJob, then Thread0 will be suspended because there are no other scheduled continuations (from await or ContinueWith) nor scheduled thread-pool jobs (from Task.Run or manual use of ThreadPool.QueueUserWorkItem).
(Remember that a suspended thread is not the same thing as a blocked thread! - See footnote 1)
So Thread1 is running BlockingJob and it sleeps (blocks) for those 5 seconds because Thread.Sleep blocks which is why you should always prefer Task.Delay in async code because it doesn't block!).
After those 5 seconds Thread1 then unblocks and returns "Done with work!" from that BlockingJob call - and it returns that value to Task.Run's internal scheduler's call-site and the scheduler marks the BlockingJob job as complete with "Done with work!" as the result value (this is represented by the Task<String>.Result value).
Thread1 then returns to the thread-pool.
The scheduler knows that there is an await that exists on that Task<String> inside DoWorkAsync that was used by Thread0 previously in step 8 when Thread0 returned to the pool.
So because that Task<String> is now completed, it picks out another thread from the thread-pool (which may or may not be Thread0 - it could be Thread1 or another different thread Thread2 - again, it depends on your program, your computer, etc - but most importantly it depends on the synchronization-context and if you used ConfigureAwait(true) or ConfigureAwait(false)).
In trivial console programs without a synchronization context (i.e. not WinForms, WPF, or ASP.NET (but not ASP.NET Core)) then the scheduler will use any thread in the pool (i.e. there's no thread affinity). Let's call this Thread2.
(I need to digress here to explain that while your async Task<String> DoWorkAsync method is a single method in your C# source code but internally the DoWorkAsync method is split-up into "sub-methods" at each await statement, and each "sub-method" can be entered into directly).
(They're not "sub-methods" but actually the entire method is rewritten into a hidden state-machine struct that captures local function state. See footnote 2).
So now the scheduler tells Thread2 to call into the DoWorkAsync "sub- method" that corresponds to the logic immediately after that await. In this case it's the String value = await threadTask; line.
Remember that the scheduler knows that the Task<String>.Result is "Done with work!", so it sets String value to that string.
The DoWorkAsync sub-method that Thread2 called-into then also returns that String value - but not to Main, but right back to the scheduler - and the scheduler then passes that string value back to the Task<String> for the await messageTask in Main and then picks another thread (or the same thread) to enter-into Main's sub-method that represents the code after await messageTask, and that thread then calls Console.WriteLine( message ); and the rest of the code in a normal fashion.
Footnotes
Footnote 1
Remember that a suspended thread is not the same thing as a blocked thread: This is an oversimplification, but for the purposes of this answer, a "suspended thread" has an empty call-stack and can be immediately put to work by the scheduler to do something useful, whereas a "blocked thread" has a populated call-stack and the scheduler cannot touch it or repurpose it unless-and-until it returns to the thread-pool - note that a thread can be "blocked" because it's busy running normal code (e.g. a while loop or spinlock), because it's blocked by a synchronization primitive such as a Semaphore.WaitOne, because it's sleeping by Thread.Sleep, or because a debugger instructed the OS to freeze the thread).
Footnote 2
In my answer, I said that the C# compiler would actually compile code around each await statement into "sub-methods" (actually a state-machine) and this is what allows a thread (any thread, regardless of its call-stack state) to "resume" a method where its thread returned to the thread-pool. This is how that works:
Supposing you have this async method:
async Task<String> FoobarAsync()
{
Task<Int32> task1 = GetInt32Async();
Int32 value1 = await task1;
Task<Double> task2 = GetDoubleAsync();
Double value2 = await task2;
String result = String.Format( "{0} {1}", value1, value2 );
return result;
}
The compiler will generate CIL (MSIL) that would conceptually correspond to this C# (i.e. if it were written without async and await keywords).
(This code omits lots of details like exception handling, the real values of state, it inlines AsyncTaskMethodBuilder, the capture of this, and so on - but those details aren't important right now)
Task<String> FoobarAsync()
{
FoobarAsyncState state = new FoobarAsyncState();
state.state = 1;
state.task = new Task<String>();
state.MoveNext();
return state.task;
}
struct FoobarAsyncState
{
// Async state:
public Int32 state;
public Task<String> task;
// Locals:
Task<Int32> task1;
Int32 value1
Task<Double> task2;
Double value2;
String result;
//
public void MoveNext()
{
switch( this.state )
{
case 1:
this.task1 = GetInt32Async();
this.state = 2;
// This call below is a method in the `AsyncTaskMethodBuilder` which essentially instructs the scheduler to call this `FoobarAsyncState.MoveNext()` when `this.task1` completes.
// When `FoobarAsyncState.MoveNext()` is next called, the `case 2:` block will be executed because `this.state = 2` was assigned above.
AwaitUnsafeOnCompleted( this.task1.GetAwaiter(), this );
// Then immediately return to the caller (which will always be `FoobarAsync`).
return;
case 2:
this.value1 = this.task1.Result; // This doesn't block because `this.task1` will be completed.
this.task2 = GetDoubleAsync();
this.state = 3;
AwaitUnsafeOnCompleted( this.task2.GetAwaiter(), this );
// Then immediately return to the caller, which is most likely the thread-pool scheduler.
return;
case 3:
this.value2 = this.task2.Result; // This doesn't block because `this.task2` will be completed.
this.result = String.Format( "{0} {1}", value1, value2 );
// Set the .Result of this async method's Task<String>:
this.task.TrySetResult( this.result );
// `Task.TrySetResult` is an `internal` method that's actually called by `AsyncTaskMethodBuilder.SetResult`
// ...and it also causes any continuations on `this.task` to be executed as well...
// ...so this `return` statement below might not be called until a very long time after `TrySetResult` is called, depending on the contination chain for `this.task`!
return;
}
}
}
Note that FoobarAsyncState is a struct rather than a class for performance reasons that I won't get into.
When you use the static async Task Main(string[] args) signature, the C# compiler generates behind the scenes a MainAsync method, and the actual Main method is rewritten like this:
public static void Main()
{
MainAsync().GetAwaiter().GetResult();
}
private static async Task MainAsync()
{
// Main body here
}
This means that the main thread of the console application, the thread having ManagedThreadId equal to 1, becomes blocked immediately after the first await of a non-completed task is hit, and remains blocked for the entire lifetime of the application! After that point the application runs exclusively on ThreadPool threads (unless your code starts threads explicitly).
This is a waste of a thread, but the alternative is to install a SynchronizationContext to the Console application, which has other drawbacks:
The application becomes susceptible to the same deadlock scenarios that plague the UI applications (Windows Forms, WPF etc).
There is nothing built-in available, so you must search for third-party solutions. Like Stephen Cleary's AsyncContext from the Nito.AsyncEx.Context package.
So the price of 1 MB of wasted RAM becomes a bargain, when you consider the complexity of the alternative!
There is another alternative though, which makes better use of the main thread. And this is to avoid the async Task Main signature. Just use the .GetAwaiter().GetResult(); after every major asynchronous method of your app. This way after the method completes you'll be back in the main thread!
static void Main(string[] args)
{
Console.WriteLine(" Fun With Async ===>");
string message = DoWorkAsync().GetAwaiter().GetResult();
Console.WriteLine(message);
Console.WriteLine($"Completed, Thread: {Thread.CurrentThread.ManagedThreadId}");
Console.ReadLine();
}

What code is actually executed "multi-threadedly" in async/await pattern?

In this code:
public async Task v_task()
{
await Task.Run(() => Console.WriteLine("Hello!"));
}
public async void v1()
{
await v_task();
// some other actions...
}
public void ButtonClick()
{
v1();
Console.WriteLine("Hi!");
}
Which methods above are actually executed in parallel in the async/await generated lower thread pool if ButtonClick is called?
I mean, what should be my concerns about race conditions working with async/await? All async methods are mandatory executed in the same caller's thread? Should I use mutex on possible shared state? If yes, how could I detect what are the shared state objects?
Which methods above are actually executed in parallel in the async/await generated lower thread pool if ButtonClick is called?
Only the Console.WriteLine within the Task.Run.
I mean, what should be my concerns about race conditions working with async/await?
I suggest you start by reading my async intro, which explains how await actually works.
In summary, async methods are usually written in a serially asynchronous fashion. Take this code for example:
CodeBeforeAwait();
await SomeOtherMethodAsync();
CodeAfterAwait();
You can always say that CodeBeforeAwait will execute to completion first, then SomeOtherMethodAsync will be called. Then our method will (asynchronously) wait for SomeOtherMethodAsync to complete, and only after that will CodeAfterAwait be called.
So it's serially asynchronous. It executes in a serial fashion, just like you'd expect it to, but also with an asynchronous point in that flow (the await).
Now, you can't say that CodeBeforeAwait and CodeAfterAwait will execute within the same thread, at least not without more context. await by default will resume in the current SynchronizationContext (or the current TaskScheduler if there is no SyncCtx). So, if the sample method above was executed in the UI thread, then you would know that CodeBeforeAwait and CodeAfterAwait will both execute on the UI thread. However, if it was executed without a context (i.e., from a background thread or Console main thread), then CodeAfterAwait may run on a different thread.
Note that even if parts of a method run on a different thread, the runtime takes care of putting any barriers in place before continuing the method, so there's no need to barrier around variable access.
Also note that your original example uses Task.Run, which explicitly places work on the thread pool. That's quite different than async/await, and you will definitely have to treat that as multithreaded.
Should I use mutex on possible shared state?
Yes. For example, if your code uses Task.Run, then you'll need to treat that as a separate thread. (Note that with await, it's a lot easier to not share state at all with other threads - if you can keep your background tasks pure, they're much easier to work with).
If yes, how could I detect what are the shared state objects?
Same answer as with any other kind of multi-threaded code: code inspection.
If you call an async function, your thread will perform this function until it reaches an await.
If you weren't using async-await, the thread would yield processing until the awaited code was finished and continue with the statement after the await.
But as you are using async-await, you told the compiler that whenever the thread has to wait for something, you have some other things it can do instead of waiting, The thread will do those other things until you say: now await until your original thing is finished.
Because of the call to an async function we are certain that somewhere inside there should be an await. Note that if you call an async function that doesn't await you get a compiler warning that the function will run synchronously.
Example:
private async void OnButton1_clickec(object sender, ...)
{
string dataToSave = ...;
var saveTask = this.MyOpenFile.SaveAsync(dataToSave);
// the thread will go into the SaveAsync function and will
// do all things until it sees an await.
// because of the async SaveAsync we know there is somewhere an await
// As you didn't say await this.MyOpenfile.SaveAsync
// the thread will not wait but continue with the following
// statements:
DoSomethingElse()
await saveTask;
// here we see the await. The thread was doing something else,
// finished it, and now we say: await. That means it waits until its
// internal await is finished and continues with the statements after
// this internal await.
Note that even if the await somewhere inside SaveAsync was finished, the thread will not perform the next statement until you await SaveTask. This has the effect that DoSomethingElse will not be interrupted if the await inside the SaveAsync was finished.
Therefore normally it's not useful to create an async function that does not return either a Task or a Task < TResult >
The only exception to this is an event handler. The GUI doesn't have to wait until your event handler is finished.

How can I have two separate task schedulers?

I am writing a game, and using OpenGL I require that some work be offloaded to the rendering thread where an OpenGL context is active, but everything else is handled by the normal thread pool.
Is there a way I can force a Task to be executed in a special thread-pool, and any new tasks created from an async also be dispatched to that thread pool?
I want a few specialized threads for rendering, and I would like to be able to use async and await for example for creating and filling a vertex buffer.
If I just use a custom task scheduler and a new Factory(new MyScheduler()) it seems that any subsequent Task objects will be dispatched to the thread pool anyway where Task.Factory.Scheduler suddenly is null.
The following code should show what I want to be able to do:
public async Task Initialize()
{
// The two following tasks should run on the rendering thread pool
// They cannot run synchronously because that will cause them to fail.
this.VertexBuffer = await CreateVertexBuffer();
this.IndexBuffer = await CreateIndexBuffer();
// This should be dispatched, or run synchrounousyly, on the normal thread pool
Vertex[] vertices = CreateVertices();
// Issue task for filling vertex buffer on rendering thread pool
var fillVertexBufferTask = FillVertexBufffer(vertices, this.VertexBuffer);
// This should be dispatched, or run synchrounousyly, on the normal thread pool
short[] indices = CreateIndices();
// Wait for tasks on the rendering thread pool to complete.
await FillIndexBuffer(indices, this.IndexBuffer);
await fillVertexBufferTask; // Wait for the rendering task to complete.
}
Is there any way to achieve this, or is it outside the scope of async/await?
This is possible and basically the same thing what Microsoft did for the Windows Forms and WPF Synchronization Context.
First Part - You are in the OpenGL thread, and want to put some work into the thread pool, and after this work is done you want back into the OpenGL thread.
I think the best way for you to go about this is to implement your own SynchronizationContext. This thing basically controls how the TaskScheduler works and how it schedules the task. The default implementation simply sends the tasks to the thread pool. What you need to do is to send the task to a dedicated thread (that holds the OpenGL context) and execute them one by one there.
The key of the implementation is to overwrite the Post and the Send methods. Both methods are expected to execute the callback, where Send has to wait for the call to finish and Post does not. The example implementation using the thread pool is that Sendsimply directly calls the callback and Post delegates the callback to the thread pool.
For the execution queue for your OpenGL thread I am think a Thread that queries a BlockingCollection should do nicely. Just send the callbacks to this queue. You may also need some callback in case your post method is called from the wrong thread and you need to wait for the task to finish.
But all in all this way should work. async/await ensures that the SynchronizationContext is restored after a async call that is executed in the thread pool for example. So you should be able to return to the OpenGL thread after you did put some work off into another thread.
Second Part - You are in another thread and want to send some work into the OpenGL thread and await the completion of that work.
This is possible too. My idea in this case is that you don't use Tasks but other awaitable objects. In general every object can be awaitable. It just has to implement a public method getAwaiter() that returns a object implementing the INotifyCompletion interface. What await does is that it puts the remaining method into a new Action and sends this action to the OnCompleted method of that interface. The awaiter is expected to call the scheduled actions once the operation it is awaiting is done. Also this awaiter has to ensure that the SynchronizationContext is captured and the continuations are executed on the captured SynchronizationContext. That sounds complicated, but once you get the hang of it, it goes fairly easy. What helped me a lot is the reference source of the YieldAwaiter (this is basically what happens if you use await Task.Yield()). This is not what you need, but I think it is a place to start.
The method that returns the awaiter has to take care of sending the actual work to the thread that has to execute it (you maybe already have the execution queue from the first part) and the awaiter has to trigger once that work is done.
Conclusion
Make no mistake. That is a lot of work. But if you do all that you will have less problem down the line because you can seamless use the async/await pattern as if you would be working inside windows forms or WPF and that is a hue plus.
First, realize that await introduces the special behavior after the method is called; that is to say, this code:
this.VertexBuffer = await CreateVertexBuffer();
is pretty much the same as this code:
var createVertexBufferTask = CreateVertexBuffer();
this.VertexBuffer = await createVertexBufferTask;
So, you'll have to explicitly schedule code to execute a method within a different context.
You mention using a MyScheduler but I don't see your code using it. Something like this should work:
this.factory = new TaskFactory(CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskContinuationOptions.None, new MyScheduler());
public async Task Initialize()
{
// Since you mention OpenGL, I'm assuming this method is called on the UI thread.
// Run these methods on the rendering thread pool.
this.VertexBuffer = await this.factory.StartNew(() => CreateVertexBuffer()).Unwrap();
this.IndexBuffer = await this.factory.StartNew(() => CreateIndexBuffer()).Unwrap();
// Run these methods on the normal thread pool.
Vertex[] vertices = await Task.Run(() => CreateVertices());
var fillVertexBufferTask = Task.Run(() => FillVertexBufffer(vertices, this.VertexBuffer));
short[] indices = await Task.Run(() => CreateIndices());
await Task.Run(() => FillIndexBuffer(indices, this.IndexBuffer));
// Wait for the rendering task to complete.
await fillVertexBufferTask;
}
I would look into combining those multiple Task.Run calls, or (if Initialize is called on a normal thread pool thread) removing them completely.

Task.Wait vs Task.RunSyncronously where task has call to WPF Dispatcher.Invoke

I have a Task that I am starting and wish to wait for completion in a WPF app. Inside this task I invoke an Action on the dispatcher.
If I use Task.Wait() it appears to hang as if the method never finished. Also, breakpoints inside the Dispatcher.Invoke are never hit.
If I use Task.RunSyncronously() it appears to work correctly and breakpoints inside the Dispatcher are hit.
Why is there a difference?
Code sample below:
public void ExampleMethod()
{
// When doing the following:
var task = new Task(LoadStuff);
// This never returns:
task.Start();
task.Wait();
// This version, however, does:
task.RunSyncronously();
}
private void LoadStuff()
{
ObservableCollection<StuffObj> stuff = Stuff.Load(arg1, true);
DispatchHelper.RunOnDispatcher(() =>
{
...
});
}
public static class DispatchHelper
{
public static void RunOnDispatcher(Action action)
{
Application.Current.Dispatcher.Invoke(action);
}
}
Yes, there's a major difference. If you use RunSyncronously you just run the task in the UI thread. If you start it up in a background thread and us Wait then the code is running in a background thread and the UI thread is blocked. If the code within that task is invoking to the UI thread, and the UI thread is being blocked (by the Wait) then you've created a deadlock, and the application will remain frozen.
Note that if you used, RunSyncronously on that task from a non-UI thread, and the UI thread was being blocked by something else, you would still see the deadlock.
Now, as for what you should do, there are really two options here:
The task itself doesn't actually take a long time, and it really should run in the UI thread rather than in a background thread. The UI thread won't be frozen (temporarily) for long enough to be a problem doing all of this work directly in the UI. If this is the case, you probably shouldn't even make it a Task, just put the code in a method and call the method.
The task does take a long time to run, and then it updates the UI after doing that work. If that is the case then it's important that it not be RunSyncronously but started in a background thread. In order to prevent your entire application from deadlocking it will mean that you'll need to not block the UI thread through a Wait call. What you need to do if you have some code that you want to run after the task finishes, is to add a continuation to the task. In C# 4.0 this could be done by calling ContinueWith on the task, and adding in a delegate to be run. In C# 5.0+ you could instead await on the relevant task (rather than Waiting on it, which is actually a big difference) and it will automatically wire up the remainder of the method to run as a continuation for you (in effect it is syntactic sugar for an explicit ContinueWith call, but it's a very useful one).

Categories