Passing ExecutionContext (flow) VS capturing it? - c#

From here:
https://devblogs.microsoft.com/premier-developer/dissecting-the-async-methods-in-c/
Methods like Task.Run or ThreadPool.QueueUserWorkItem do this automatically. Task.Run method captures ExecutionContext from the invoking thread and stores it with the Task instance. When the TaskScheduler associated with the task runs a given delegate, it runs it via ExecutionContext.Run using the stored context.
What the difference between normal ExecutionContext flow and capture? To me, capture means very specific thing - a snapshot of the state of the object. Like, closure captures in anonymous methods/local methods/etc where it is generated as field and has its own value no longer reflecting the state of original value source (variable).
So when we talk about capturing context of the current thread - is this different from normal context flow? Like, during normal flow the state is always up to date so if culture on parent thread changes the child is aware, but if EC is captured - now the child thread just gets the snapshot of the capture of that time? Do I understand it correctly? MSDN docs are really inconsistent in the "flow"/"capture" term usage.
Now, we have a very important observation to make: flowing ExecutionContext is semantically very different than capturing and posting to a SynchronizationContext.
https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/
When you flow ExecutionContext, you’re capturing the state from one thread and then restoring that state such that it’s ambient during the supplied delegate’s execution. That’s not what happens when you capture and use a SynchronizationContext. The capturing part is the same, in that you’re grabbing data from the current thread, but you then use that state differently. Rather than making that state current during the invocation of the delegate, with SynchronizationContext.Post you’re simply using that captured state to invoke the delegate. Where and when and how that delegate runs is completely up to the implementation of the Post method.
This makes little sense to me. The idea behind capturing context and posting on it (in the example of UI app with UI thread) is to get back tщ that thread in the same context (to update UI, duh). But if "Where and when and how that delegate runs is completely up to the implementation of the Post method." then how can you even ensure that?

This is a notoriously complex topic and the article by Stephen Toub is (while excellent) very difficult to understand.
The ExecutionContext contains "ambient" information about the your execution environment. That is, for example the SecurityContext (which is kept inside the EC) will be 'flowed' which means you'll have same security context inside the threadpool thread as you do in your UI thread. It aligns a bunch of other details like this so that execution occur in the same environment.
Capturing vs flowing in this case is the difference between holding a reference to the context vs making that context the current context. So when we run something on a task like this:
public static void DoWork()
{
var sc = SynchronizationContext.Current; <-- SC of UI thread is captured, so a reference has been created to the SC of the UI thread
ThreadPool.QueueUserWorkItem(delegate
{
var scThreadPool = SynchronizationContext.Current; <-- thread pool thread has no SC associated so this is null
… // do work on ThreadPool
sc.Post(delegate <-- this is posting to the SC of the UI thread now
{
… // do work on the original context
}, null);
});
}
If you were to capture the SynchronisationContext.Current inside the delegate you'd get the current synchronisation context of the threadpool thread (in this it would be null since they don't have one associated).
If the synchronisation conext were to flow then we would get the UI thread SC instead of null when calling
var scThreadPool = SynchronizationContext.Current;
So the execution context flows, which means it becomes the execution context of the thread pool thread but the SC doesn't. The last confusing thing is that the SC is actually part of the execution context and can (but usually doesn't) flow. In the majority of cases it won't flow and we don't need to worry about it.

Related

What thread runs a Task's continuation if you don't await the task?

I'm trying to wrap my head around control flow in C# when using async, Task, and await.
I understand how promises work, and that the returned Task<> from an async method will eventually contain the result of a computation/IO/whatever.
I think I understand that if you explicitly wait for that Task, then the current thread blocks until the Task is complete. I also think that means that the code in the async method that returns a Task will be running on a thread in a thread pool.
What I don't understand is what happens if I don't "await" the Task returned by an asynchronous method. It seems to me that the continuation is executed on the original thread that calls the async method, but I have no idea how control can return to that thread.
Here's an example. Here's I'm using UniTask which is basically Tasks for Unity:
public async UniTask ConnectAsync(Connection connection)
{
Debug.Log(Thread.CurrentThread.Name); -> this prints "Main Thread"
// Close Any Old Connections
await DisconnectAsync();
// Default Address
if (string.IsNullOrEmpty(connection.Address)) { connection.Address = "localhost:6379"; }
// Connect
ConfigurationOptions config = new()
{
EndPoints =
{
{ connection.Address, connection.Port },
},
User = connection.Username,
Password = connection.Password,
};
m_Connection = await ConnectionMultiplexer.ConnectAsync(config);
// Create Graph Client
m_Graph = new(m_Connection.GetDatabase());
// Notify
await Editor.Controller.OnConnect();
Debug.Log(Thread.CurrentThread.Name); -> this prints "Main Thread"
}
If I call this method, and then neglect to await the returned Task (UniTask), both Debug.Log() show that execution is happening on the "Main Thread" (i.e. the UI thread).
How is it that without awaiting this Task, the Main Thread is able to return to this continuation? Does C# wait until the thread is in the Suspended/WaitSleepJoin state? I'm not aware of any code putting the UI thread to sleep so I'm not sure about that. I'm certainly not putting the UI to sleep.
EDIT: I believe the chosen answer basically answered the question in the final sentence:
"Your code just needs to return to the main loop to allow the
continuation to run."
In other words, there's a loop somewhere deep in the bowels of (Unity in this case) and if the UI thread gets there, then it takes the opportunity to continue any pending tasks. (Please correct me in a comment if this is wrong and I'll update accordingly).
Incidentally, these links were very informative:
https://blog.stephencleary.com/2013/11/there-is-no-thread.html
https://www.ncameron.org/blog/async-io-fundamentals/
https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/
https://blog.stephencleary.com/2012/02/async-and-await.html
What I don't understand is what happens if I don't "await" the Task returned by an asynchronous method. It seems to me that the continuation is executed on the original thread that calls the async method, but I have no idea how control can return to that thread.
As I describe on my blog, each await (by default) captures a "context", which is SynchronizationContext.Current or TaskScheduler.Current. In this particular case, the UI context is captured and used to resume the async method (i.e., execute the continuation).
How is it that without awaiting this Task, the Main Thread is able to return to this continuation? Does C# wait until the thread is in the Suspended/WaitSleepJoin state?
It has to do with contexts, not threads. The UI context schedules work by posting to the main UI message queue. So the continuation is run when the UI thread processes its message queue; it doesn't have anything to do with thread states.
I'm not aware of any code putting the UI thread to sleep so I'm not sure about that. I'm certainly not putting the UI to sleep.
Your code just needs to return to the main loop to allow the continuation to run.
I understand how promises work
Good, then we can stop right there. Tasks are nothing but compiler syntactic sugar over a promise. In fact, when JavaScript copied the await/async keywords from C#, they got implemented over the native Promise object.
Now, for the remainder of this I'm going to assume that you don't know how promises work. Think of it as getting called out on your promise bluff on your CV.
There's three parts to an async method:
The "synchronous" part. This is what will run when you simply call your async function, awaiting it or not, and is everything before the first await in your function. In your function this is the Debug.Log call and the synchronous part of DisconnectAsync.
The "asynchronous" part, the tail of your function. This gets stored as a lambda and it captures all necessary variables on creation. This gets called after #1 and when "done" it returns the Task object from your function. When the task is fully completed, the task is set as completed. Note that this can be recursive if you have multiple tails inside your tail.
All the magic of Task. For example, Task.WhenAll instantiates mutexes in your Task and then waits on them for completion. This makes Task technically disposable, and thus a memory and OS handle leak if you don't dispose every single task you create. await itself is handled through TaskCompletionSource, and you get just the task it manages. Things like that.
Note that nowhere in this did I mention threads. Tasks are to threads like what cats are to doctors. They both exist, some interact, but you have to be pretty insane to say cats are made to work only with doctors. Instead, tasks work on contexts. Thread pools are one default context. Another is single threaded contexts.
That's right, you can easily have async code run on a single thread, which is perfect for GUI in a single threaded render loop-driven game. You create a dialog, await its showing and get a result, all without any additional threads. You start an animation and await its completion, all without any threads.

Make Task.Delay continue on the calling thread

Consider this method:
//Called on a known thread
public async void ThreadSleep()
{
while(itemsInQueue)
{
//This call is currently on Thread X
await Task.Delay(5000);
//This needs to be on the thread that the method was called on
DoSomeProcessing();
}
}
I am assuming that the Task.Delay is executing async on a different thread and resumes on that same thread. This was not very obvious to me. How do I get the method to continue on Thread X?
PS: The ThreadSleep method executes on a non UI thread
Edit: 1) Added W.Brian's code example for simplicity.
2) Yes, this example is exactly that... an example.
3) The purpose of Thread.Delay is just to add some delay between processing.
You need to create your own synchronization context (like the UI thread does).
There's a pretty good article on MSDN that helps to understand the problem and how to create a solution.
Mind if I ask why you have to continue on the same thread?
Usually it shouldn't create and issues when a new thread is used since the context is preserved.
If you need to preserve some kind of context between calls at a deeper level (like you would do with ThreadLocal), I suggest you use the new AsyncLocal to achieve this goal.
It makes sure that immutable objects stay within the async context even if the thread is changed (refer to: How do the semantics of AsyncLocal differ from the logical call context?).
await Task.Delay(5000).ConfigureAwait(true);
Calling ConfigureAwait(true) should work as it ensures the same context as the original thread even if the thread changes. This assumes that ThreadLocal<T> is not being used, in which case async/await will generally cause problems and Thread.Sleep may be preferred if you can't change the rest of the code.

Are continuations queued on a Task always executed on the thread that finishes the task?

I'm trying to implement coroutines using async/await, and for that I want to ensure my coroutines are only executing on one thread (the thread that resumes them).
I am currently using a custom awaiter which simply queues the continuation on the coroutine object. When a coroutine wants to yield, it awaits this custom awaiter. When a coroutine is resumed, it simply calls the continuation.
I can guarantee that only one continatuion is queued per resume, ie. that we don't create multiple tasks without awaiting them. I can also guarantee that we will only be awaiting tasks that ultimately await the custom awaiter or other tasks that await the custom awaiter. That is, we won't be awating any "external" tasks.
An example would be something like this:
private static async Task Sleep(int ms)
{
Stopwatch timer = Stopwatch.StartNew();
do
{
await Coroutine.Yield();
}
while (timer.ElapsedMilliseconds < ms);
}
private static async Task Test()
{
// Second resume
await Sleep(1000);
// Unknown how many resumes
}
private static async Task Main()
{
// First resume
await Coroutine.Yield();
// Second resume
await Test();
}
It all seems to work and it seems like the continuations to the tasks are indeed executed inline on the same thread. I just want to be sure that this behavior is consistent and that I can rely on it.
I was able to check the reference source, and have found what I think is the place continuations are executed. The path to this function seems pretty complex though, and I cannot determine just what call exactly leads to this function being called (but I assume it is some compiler generated call).
Now, from this function, it seems like the continuation is not inlined if:
The current thread is aborting
This should not be a problem, as the current thread is willingly executing the coroutine, and we shouldn't be executing a coroutine if we're aborting.
IsValidLocationForInlining is false
This property is false if the current synchronization context is non default, or the current task scheduler is non default. As a precaution I am doing SynchronizationContext.SetSynchronizationContext(null) for the duration of the continuation, when resuming a coroutine. I will also be ensuring that the task scheduler is the default.
Now, my actual question is if I can rely on this behavior. Is this something that is likely to change in .NET versions? Would it be better to implement a custom synchronization context which ensured that all continuations were run by the coroutine?
Furthermore, I know the task libraries have changed a lot from .NET 4 to .NET 4.5. The reference source is for .NET 4.5, as far as I know, so I want to know if someone knows if this behavior has changed. I will be using the coroutines library on .NET 4.0 with Microsoft.Bcl.Async mainly, and it also seems to work fine here.
I'm trying to implement coroutines using async/await, and for that I
want to ensure my coroutines are only executing on one thread (the
thread that resumes them).
I think you can safely rely upon this behavior. This should be true as long as you do not use any of the following features:
Custom TPL task schedulers;
Custom synchronization contexts;
ConfiguredTaskAwaitable or Task.ConfigureAwait();
YieldAwaitable or Task.Yield();
Task.ContinueWith();
Anything which may lead to a thread switch, like an async I/O API, Task.Delay(), Task.Run(), Task.Factory.StartNew(), ThreadPool.QueueUserWorkItem() etc.
The only thing that you use here is TaskAwaiter, more about it below.
First of all, you should not be worried about a thread switch inside the task, where you do await Coroutine.Yield(). The code will be resumed exactly on the same thread where you explicitly call the continuation callback, you have complete control over this.
Secondly, the only Task object you have here is that generated by the state machine logic (specifically, by AsyncTaskMethodBuilder). This is the task returned by Test(). As mentioned above, a thread switch inside this task may not take place, unless you do it explicitly before calling the continuation callback via your custom awaiter.
So, your only remaining concerned is about a thread switch which may happen at the point where you're awaiting the result of the task returned by Test(). That's where TaskAwaiter comes into play.
The behavior of TaskAwaiter is undocumented. As far as I can tell from the Reference Sources, IsValidLocationForInlining is not observed for the TaskContinuation kind of continuation (created by TaskAwaiter). The present behavior is the following: the continuation will be not inlined if the current thread was aborted or if the current thread's synchronization context is different from that captured by TaskAwaiter.
If you don't want to rely upon this, you can create another custom awaiter to replace TaskAwaiter for your coroutine tasks. You could implement it using Task.ContinueWith( TaskContinuationOptions.ExecuteSynchronously), which behavior is unofficially documented by Stephen Toub in his "When "“ExecuteSynchronously” doesn't execute synchronously" blog post. To sum up, the ExecuteSynchronously continuation won't be inlined under the following severe conditions:
the current thread was aborted;
the current thread has stack overflows;
the target task scheduler rejects inlining (but you're not using custom task schedulers; the default one always promotes inlining where possible).

What does SynchronizationContext do?

In the book Programming C#, it has some sample code about SynchronizationContext:
SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(#"c:\temp\log.txt");
originalContext.Post(delegate {
myTextBox.Text = text;
}, null);
});
I'm a beginner in threads, so please answer in detail.
First, I don't know what does context mean, what does the program save in the originalContext? And when the Post method is fired, what will the UI thread do?
If I ask some silly things, please correct me, thanks!
EDIT: For example, what if I just write myTextBox.Text = text; in the method, what's the difference?
What does SynchronizationContext do?
Simply put, SynchronizationContext represents a location "where" code might be executed. Delegates that are passed to its Send or Post method will then be invoked in that location. (Post is the non-blocking / asynchronous version of Send.)
Every thread can have a SynchronizationContext instance associated with it. The running thread can be associated with a synchronization context by calling the static SynchronizationContext.SetSynchronizationContext method, and the current context of the running thread can be queried via the SynchronizationContext.Current property.
Despite what I just wrote (each thread having an associated synchronization context), a SynchronizationContext does not necessarily represent a specific thread; it can also forward invocation of the delegates passed to it to any of several threads (e.g. to a ThreadPool worker thread), or (at least in theory) to a specific CPU core, or even to another network host. Where your delegates end up running is dependent on the type of SynchronizationContext used.
Windows Forms will install a WindowsFormsSynchronizationContext on the thread on which the first form is created. (This thread is commonly called "the UI thread".) This type of synchronization context invokes the delegates passed to it on exactly that thread. This is very useful since Windows Forms, like many other UI frameworks, only permits manipulation of controls on the same thread on which they were created.
What if I just write myTextBox.Text = text; in the method, what's the difference?
The code that you've passed to ThreadPool.QueueUserWorkItem will be run on a thread pool worker thread. That is, it will not execute on the thread on which your myTextBox was created, so Windows Forms will sooner or later (especially in Release builds) throw an exception, telling you that you may not access myTextBox from across another thread.
This is why you have to somehow "switch back" from the worker thread to the "UI thread" (where myTextBox was created) before that particular assignment. This is done as follows:
While you are still on the UI thread, capture Windows Forms' SynchronizationContext there, and store a reference to it in a variable (originalContext) for later use. You must query SynchronizationContext.Current at this point; if you queried it inside the code passed to ThreadPool.QueueUserWorkItem, you might get whatever synchronization context is associated with the thread pool's worker thread. Once you have stored a reference to Windows Forms' context, you can use it anywhere and at any time to "send" code to the UI thread.
Whenever you need to manipulate a UI element (but are not, or might not be, on the UI thread anymore), access Windows Forms' synchronization context via originalContext, and hand off the code that will manipulate the UI to either Send or Post.
Final remarks and hints:
What synchronization contexts won't do for you is telling you which code must run in a specific location / context, and which code can just be executed normally, without passing it to a SynchronizationContext. In order to decide that, you must know the rules and requirements of the framework you're programming against — Windows Forms in this case.
So remember this simple rule for Windows Forms: DO NOT access controls or forms from a thread other than the one that created them. If you must do this, use the SynchronizationContext mechanism as described above, or Control.BeginInvoke (which is a Windows Forms-specific way of doing exactly the same thing).
If you're programming against .NET 4.5 or later, you can make your life much easier by converting your code that explicitly uses SynchronizationContext, ThreadPool.QueueUserWorkItem, control.BeginInvoke, etc. over to the new async / await keywords and the Task Parallel Library (TPL), i.e. the API surrounding the Task and Task<TResult> classes. These will, to a very high degree, take care of capturing the UI thread's synchronization context, starting an asynchronous operation, then getting back onto the UI thread so you can process the operation's result.
I'd like to add to other answers, SynchronizationContext.Post just queues a callback for later execution on the target thread (normally during the next cycle of the target thread's message loop), and then execution continues on the calling thread. On the other hand, SynchronizationContext.Send tries to execute the callback on the target thread immediately, which blocks the calling thread and may result in deadlock. In both cases, there is a possibility for code reentrancy (entering a class method on the same thread of execution before the previous call to the same method has returned).
If you're familiar with Win32 programming model, a very close analogy would be PostMessage and SendMessage APIs, which you can call to dispatch a message from a thread different from the target window's one.
Here is a very good explanation of what synchronization contexts are:
It's All About the SynchronizationContext.
It stores the synchronization provider, a class derived from SynchronizationContext. In this case that will probably be an instance of WindowsFormsSynchronizationContext. That class uses the Control.Invoke() and Control.BeginInvoke() methods to implement the Send() and Post() methods. Or it can be DispatcherSynchronizationContext, it uses Dispatcher.Invoke() and BeginInvoke(). In a Winforms or WPF app, that provider is automatically installed as soon as you create a window.
When you run code on another thread, like the thread-pool thread used in the snippet, then you have to be careful that you don't directly use objects that are thread-unsafe. Like any user interface object, you must update the TextBox.Text property from the thread that created the TextBox. The Post() method ensures that the delegate target runs on that thread.
Beware that this snippet is a bit dangerous, it will only work correctly when you call it from the UI thread. SynchronizationContext.Current has different values in different threads. Only the UI thread has a usable value. And is the reason the code had to copy it. A more readable and safer way to do it, in a Winforms app:
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(#"c:\temp\log.txt");
myTextBox.BeginInvoke(new Action(() => {
myTextBox.Text = text;
}));
});
Which has the advantage that it works when called from any thread. The advantage of using SynchronizationContext.Current is that it still works whether the code is used in Winforms or WPF, it matters in a library. This is certainly not a good example of such code, you always know what kind of TextBox you have here so you always know whether to use Control.BeginInvoke or Dispatcher.BeginInvoke. Actually using SynchronizationContext.Current is not that common.
The book is trying to teach you about threading, so using this flawed example is okayish. In real life, in the few cases where you might consider using SynchronizationContext.Current, you'd still leave it up to C#'s async/await keywords or TaskScheduler.FromCurrentSynchronizationContext() to do it for you. But do note that they still misbehave the way the snippet does when you use them on the wrong thread, for the exact same reason. A very common question around here, the extra level of abstraction is useful but makes it harder to figure out why they don't work correctly. Hopefully the book also tells you when not to use it :)
The purpose of the synchronization context here is to make sure that myTextbox.Text = text; gets called on the main UI thread.
Windows requires that GUI controls be accessed only by the thread they were created with. If you try assign the text in a background thread without first synchronizing (through any of several means, such as this or the Invoke pattern) then an exception will be thrown.
What this does is save the synchronization context prior to creating the background thread, then the background thread uses the context.Post method execute the GUI code.
Yes, the code you've shown is basically useless. Why create a background thread, only to immediately need to go back to the main UI thread? It's just an example.
SynchronizationContext basically is a provider of callback delegates' execution. It is responsible for ensuring that the delegates are run in a given execution context after a particular portion of code (encapsulated inside a Task object in .Net TPL) in a program has completed its execution.
From technical point of view, SC is a simple C# class that is oriented to support and provide its function specifically for Task Parallel Library objects.
Every .Net application except for console applications has a tailored implementation of this class based on the specific underlying framework, eg: WPF, WindowsForm, Asp Net, Silverlight, etc.
The importance of this object is bound to the synchronization between results returning from asynchronous execution of code, and the execution of dependent code that is waiting for results from that asynchronous work.
And the word "context" stands for execution context. That is, the current execution context where that waiting code will be executed- namely the synchronization between async code and its waiting code happens in a specific execution context. Thus this object is named SynchronizationContext.
It represents the execution context that will look after syncronization of async code and waiting code execution.
To the Source
Every thread has a context associated with it -- this is also known as the "current" context -- and these contexts can be shared across threads. The ExecutionContext contains relevant metadata of the current environment or context in which the program is in execution. The SynchronizationContext represents an abstraction -- it denotes the location where your application's code is executed.
A SynchronizationContext enables you to queue a task onto another context. Note that every thread can have its own SynchronizatonContext.
For example: Suppose you have two threads, Thread1 and Thread2. Say, Thread1 is doing some work, and then Thread1 wishes to execute code on Thread2. One possible way to do it is to ask Thread2 for its SynchronizationContext object, give it to Thread1, and then Thread1 can call SynchronizationContext.Send to execute the code on Thread2.
SynchronizationContext provides us a way to update a UI from a different thread (synchronously via the Send method or asynchronously via the Post method).
Take a look at the following example:
private void SynchronizationContext SyncContext = SynchronizationContext.Current;
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(Work1);
thread.Start(SyncContext);
}
private void Work1(object state)
{
SynchronizationContext syncContext = state as SynchronizationContext;
syncContext.Post(UpdateTextBox, syncContext);
}
private void UpdateTextBox(object state)
{
Thread.Sleep(1000);
string text = File.ReadAllText(#"c:\temp\log.txt");
myTextBox.Text = text;
}
SynchronizationContext.Current will return the UI thread's sync context. How do I know this? At the start of every form or WPF app, the context will be set on the UI thread. If you create a WPF app and run my example, you'll see that when you click the button, it sleeps for roughly 1 second, then it will show the file's content. You might expect it won't because the caller of UpdateTextBox method (which is Work1) is a method passed to a Thread, therefore it should sleep that thread not the main UI thread, NOPE! Even though Work1 method is passed to a thread, notice that it also accepts an object which is the SyncContext. If you look at it, you'll see that the UpdateTextBox method is executed through the syncContext.Post method and not the Work1 method. Take a look at the following:
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(1000);
string text = File.ReadAllText(#"c:\temp\log.txt");
myTextBox.Text = text;
}
The last example and this one executes the same. Both doesn't block the UI while it does it jobs.
In conclusion, think of SynchronizationContext as a thread. It's not a thread, it defines a thread (Note that not all thread has a SyncContext). Whenever we call the Post or Send method on it to update a UI, it's just like updating the UI normally from the main UI thread. If, for some reasons, you need to update the UI from a different thread, make sure that thread has the main UI thread's SyncContext and just call the Send or Post method on it with the method that you want to execute and you're all set.
Hope this helps you, mate!
This example is from Linqpad examples from Joseph Albahari but it really helps in understanding what Synchronization context does.
void WaitForTwoSecondsAsync (Action continuation)
{
continuation.Dump();
var syncContext = AsyncOperationManager.SynchronizationContext;
new Timer (_ => syncContext.Post (o => continuation(), _)).Change (2000, -1);
}
void Main()
{
Util.CreateSynchronizationContext();
("Waiting on thread " + Thread.CurrentThread.ManagedThreadId).Dump();
for (int i = 0; i < 10; i++)
WaitForTwoSecondsAsync (() => ("Done on thread " + Thread.CurrentThread.ManagedThreadId).Dump());
}

Control.Invoke vs Tasks with a TaskScheduler

I've looked all over and I can't find an answer.
Is it better, worse, or indifferent to use:
{
...
RefreshPaintDelegate PaintDelegate = new RefreshPaintDelegate(RefreshPaint);
Control.Invoke(PaintDelegate);
}
protected void RefreshPaint()
{
this.Refresh();
}
...or...
Task.Factory.StartNew(() =>
{
this.Refresh();
},
CancellationToken.None,
TaskCreationOptions.None,
uiScheduler);
Assuming that uiScheduler is a scheduler that will delegate the calls to the UI thread, I would say that functionally, using the two is indifferent (with the exception that the call to Control.Invoke will block until the call completes, whereas the call to Task will not, however, you can always use Control.BeginInvoke to make them semantically equivalent).
From a semantic point of view, I'd say that using Control.Invoke(PaintDelegate) is a much better approach; when using a Task you are making an implicit declaration that you want to perform a unit of work, and typically, that unit of work has the context of being scheduled along with other units of work, it's the scheduler that determines how that work is delegated (typically, it's multi-threaded, but in this case, it's marshaled to the UI thread). It should also be said that there is no clear link between the uiScheduler and the Control which is linked to the UI thread that the call should be made one (typically, they are all the same, but it's possible to have multiple UI threads, although very rare).
However, in using Control.Invoke, the intention of what you want to do is clear, you want to marshal the call to the UI thread that the Control is pumping messages on, and this call indicates that perfectly.
I think the best option, however, is to use a SynchronizationContext instance; it abstracts out the fact that you need to synchronize calls to that context, as opposed to the other two options, which are either ambiguous about the intent in the call (Task) or very specific in the way it is being done (Control.Invoke).
It is not same. First version will block the calling thread until UI thread is ready to invoke the method. For a non blocking version, you should use Control.BeginInvoke, which also returns immediately.
Apart from that (if you are comparing Task to a Thread Pool thread), there is little difference in using them.
[Edit]
In this case, there is no difference between Task.Factory.StartNew and Control.BeginInvoke (but not Invoke as I wrote above), since there is only a single GUI thread which can execute your code. No matter how many calls you make using either of them, they will still execute sequentially when UI thread becomes free.

Categories