14

At many blogs, tutorials and MSDN I can read that accessing UI elements from non-UI threads is impossible - ok, I'll get an unauthorized exception. To test it I've written a very simple example:

// simple text to which TextBlock.Text is bound
private string sample = "Starting text";
public string Sample
{
    get { return sample; }
    set { sample = value; RaiseProperty("Sample"); }
}

private async void firstButton_Click(object sender, RoutedEventArgs e)
{
    await Job(); // asynchronous heavy job
    commands.Add("Element"); // back on UI thread so this should be ok?
}

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?
}

I've a Task which is being run asynchronously. In that Task I want to change my property (which will raise PropertyChanged) and add element to ObservableCollection. As it is run async, I shouldn't be able to do that, but I get no exception and the code is working fine. Thus my doubts and misunderstanding:

  • why don't I get an exception?
  • is it ok to raise PropertyChanged in async Task?
  • is it ok to modify ObservableCollection in async Task, or should I return Task<ICollection> and after obtaining the result modify the ObservableCollection- Clear it and Fill it?
  • when am I in Task on UI thread and when not?
  • 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?

To test it more I've put my property change and collection modification in other thread:

System.Threading.Timer newThreadTimer = new System.Threading.Timer((x) =>
   {
       Sample = "Changed";  // not UI thread - exception
       commands.Add("Element from async"); // not UI thread - exception
   }, null, 1000, Timeout.Infinite);

In above code my thinking is ok - just after the first or second line I get an exception. But what with the first code? Is it only a luck that my Taskwas run on UI thread?

I suspect that this is very basic thing and my misunderstanding, but I need some clarification and thus this question.

Kristof U.
  • 1,263
  • 10
  • 17
Romasz
  • 29,662
  • 13
  • 79
  • 154

2 Answers2

12

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:

  1. Your async method is currently executing from a thread different then the UI thread (either a ThreadPool thread or a new Thread)
  2. 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)

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • I'd +1 this if not this statement: *"The Task Parallel Library uses a `TaskScheduler` to implicitly (or explicitly...) capture the current `SynchronizationContext`..."* This is not quite correct for `await` continuations: http://stackoverflow.com/q/23071609/1768303 – noseratio May 02 '14 at 07:45
  • Long answer (thank you for that), but I've some doubts: 1. Look that my Job is run as asynchronous `await Job();` from Click - so captured UI context is before and after not inside? 2. I'm confused with returning from await on the same Thread - for example [I cannot await inside Mutex](http://stackoverflow.com/q/23153155/2681948), hence after returning 'the captured context might well mean any thread-pool thread at all'. 3. If is it ok to raise property changed from async Task (on different thread), then why not the Timer? – Romasz May 02 '14 at 07:54
  • 2
    @Romasz you are confusing the related by different concepts of `asynchronous`/`synchronous` and `concurrent`. All of the code you have written is SINGLE THREADED. Which means you have no problem. The beginner mistake with `async`/`await` is to think, right...we are asynchronous, that means threads right? Threads mean `concurrent`, and in the past that was the only way to achieve `asynchronous`. – Aron May 02 '14 at 08:02
  • Also to make things even more fun! I noticed that you are using WPF. Which, although is does not allow you to update UI elements on another thread. WPF automagically marshals data when using the `binding` class. – Aron May 02 '14 at 08:04
  • @Romasz As @Aron said, you are not marshaling work do a background thread when using the `async` keyword. Meanwhile, your timer is delegating work the a `ThreadPool` thread, which wont let it you update UI elements. – Yuval Itzchakov May 02 '14 at 08:06
  • @Noseratio how would you rephrase that statement that would capture the effect you talked about in your question? – Yuval Itzchakov May 02 '14 at 08:08
  • @Aron Ok that gives me some light, but is async/await always run on the same thread? And so why I cannot use await in `lock` or release `Mutex`, if it's the same thread? – Romasz May 02 '14 at 08:09
  • 1
    maybe we should continue this in a chat? http://chat.stackoverflow.com/rooms/51870/async-talk – Yuval Itzchakov May 02 '14 at 08:10
  • @YuvalItzchakov, I'd not mention `TaskScheduler` in the first sentence. The synchronization context capturing behavior you described there is carried by [`TaskAwaiter`](http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx), which implements an [awaiter](http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx?Redirected=true) for C# `async/await` compiler infrastructure. – noseratio May 02 '14 at 08:18
  • 3
    @Noseratio Changed it, hope that describes the process correctly this time :) – Yuval Itzchakov May 02 '14 at 08:24
  • In contrast to this answer, I update the UI from `await Task.Run(()=>CurrentProgress = incrementingValue)).ConfigureAwait(false); await Task.Run(()=>Task.Delay(2000)).ConfigureAwait(false);` in a loop, where property CurrentProgress's setter raises PropertyChanged. I am thus inclined to agree with [jnovo's answer](https://stackoverflow.com/a/23422983/5569922) instead, explaining that `PropertyChanged` is special in WPF and automatically dispatched to UI thread, regardless of where it was raised. – John Doe Jun 30 '20 at 13:31
4

The PropertyChanged event is automatically dispatched to the UI thread by WPF so you can modify properties that raise it from any thread. Before .NET 4.5 this was not the case for ObservableCollection.CollectionChanged event and thus, adding or removing elements from a thread other than the UI one, would cause an exception.

Starting in .NET 4.5 CollectionChanged is also automatically dispatched to the UI thread so you don't need to worry about that. This being said, you'll still need to access UI elements (such as a button for instance) from the UI thread.

jnovo
  • 5,659
  • 2
  • 38
  • 56
  • 2
    Correction. WPF always dispatched to the UI thread via bindings. The problem is that it has never supported collections being changed outside of the UI thread. This typically manifests in the `CollectionViewSource` class not supporting blah blah blah. – Aron May 02 '14 at 08:08