When awaiting on a Task
, the SynchronizationContext
of the current thread is captured (specifically in the case of Task
by the TaskAwaiter
). The continutation is then marshaled back to that SynchronizationContext
to execute the rest of the method (the part after the await
keyword).
Lets look at your code example:
private async Task Job()
{
// I'm not on UI Thread ?
await Task.Delay(2000); // some other job
Sample = "Changed"; // not ok as not UI thread?
commands.Add("Element from async"); // also not ok?
}
When you await Task.Delay(2000)
, the compiler implicitly captures the SynchronizationContext
, which is currently your WindowsFormsSynchronizationContext
. When the await returns, the continuation is executed in the same context, since you didn't explicitly tell it not to, which is your UI thread.
If you changed your code to await Task.Delay(200).ConfigureAwait(false)
, the continuation would not be marshalled back to your current SynchronizationContext
, and would run a ThreadPool
thread, causing your UI element update to throw an exception.
In your timer example, the Elapsed
event is raised via a ThreadPool
thread, hence why you get an exception that you are trying to update an element which is controlled by a different thread.
Now, let's go over your questions one by one:
why don't I get exception?
As said, the await Task.Delay(2000)
executed the Continuation on the UI thread, which made it possible to update your controls.
is it ok to Raise properties in async Task?
I am not sure what you mean by "Raise properties", but if you mean raise a INotifyPropertyChanged
event, then yes, it is ok to execute them not in a UI thread context.
is it ok to modify ObservableCollecition in async Task, or should I return Task and after obtaining the result modify Observable - Clear it and Fill it?
If you have an async
method and you want to update a UI bound element, make sure you marshal the continuation on the UI thread. If the method is called from the UI thread and you await
its result, then the continuation will implicitly be ran on your UI thread. In case you want to offload work to a background thread via Task.Run
and make sure your continuation is ran on the UI, you can capture your SynchronizationContext
using TaskScheduler.FromCurrentSynchronizationContext()
and explicitly pass it the continuation
when am I in Task on UI thread and when not?
A Task
is a promise of work that will be done in the future. when you await
on a TaskAwaitable
from the UI thread context, then you are still running on the UI thread. You are not in the UI thread if:
- Your async method is currently executing from a thread different then the UI thread (either a
ThreadPool
thread or a newThread
) - You offload work to a background ThreadPool thread using
Task.Run
.
in the code above in firstButton_Click is it ok to manage UI elements after awaiting the Task? Am I always back on UI thread?
You will be back to the UI thread as long as you don't explicitly tell your code not to return to its current context using ConfigureAwait(false)