Raising a PropertyChanged in an Asynchronous Task and UI Thread

I can read in many blogs, tutorials, and MSDN that accessing user interface elements from a thread other than the UI is not possible - well, I will get an unauthorized exception. To test this, I wrote 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 have a task that runs asynchronously, in this task I want to: change my property (which will increase the PropertyChanged property) and add an element to the ObservableCollection. Since it starts async, I should not do this, but I am not getting an exception, and the code is working fine. Thus, my doubts and misunderstanding:

  • Why am I not getting an exception?
  • Is it possible to raise PropertyChanged in async Task?
  • Is it normal to modify the ObservableCollecition in async Task, or do I need to return the Task and after receiving the result change the Observable - clear it and fill it?
  • When am I in a task about a user interface, and when not?
  • in the above code in firstButton_Click ok to manage user interface elements after waiting for a task? Have I always returned to the user interface thread?

To test it more, I put the change of the property and collection into another 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 the above code, my thinking is fine - right after the first or second line I get an exception. But what about the first code? Is it just luck that my task was run in a user interface thread?

I suspect that this is a very simple thing and my misunderstanding, but I need some clarification and therefore this question.

+7
multithreading c # asynchronous async-await
source share
2 answers

While waiting for a Task , the SynchronizationContext current thread is captured (especially in the case of Task TaskAwaiter ). Then the continuum then returns to that SynchronizationContext to execute the rest of the method (the part after the await keyword).

Let's look at your sample code:

 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 wait is returned, the continuation is executed in the same context, since you did not explicitly specify this, but this is your UI thread.

If you changed your code to await Task.Delay(200).ConfigureAwait(false) , the continuation will not be redirected back to your current SynchronizationContext and will start the ThreadPool thread, as a result of which the update of the user interface element will throw an exception.

In the example of your timer, the Elapsed event is Elapsed by the ThreadPool thread, so you get an exception that you are trying to update, an element that is being controlled by another thread.

Now let's look at your questions one by one:

Why am I not getting an exception?

As said, await Task.Delay(2000) performed the Continuation in the user interface thread, which allowed updating the controls.

Is it possible to enhance properties in async Task?

I'm not sure what you mean by "Raise properties", but if you mean raising the INotifyPropertyChanged event, then yes, it’s normal to execute them not in the context of the user interface thread.

Is it okay to modify the ObservableCollecition in async Task, or do I need to return the Task and after receiving the result change the Observable - Clear this and fill it?

If you have an async method and you want to update the associated UI element, make sure you marshal the continuation in the user interface thread. If the method is called from the user interface thread, and you await its result, then the continuation will be implicitly launched in your user interface thread. If you want to unload the work into the background thread via Task.Run and make sure that your continuation is running in the user interface, you can write your SynchronizationContext using TaskScheduler.FromCurrentSynchronizationContext() and explicitly pass the continuation to it

when I'm in Task in the UI thread, and when not?

A Task is a promise to work that will be made in the future. when you are await on TaskAwaitable from the context of the user interface thread, you are still working in the user interface thread. You are not in the user interface thread if:

  • Your asynchronous method is currently executing from a thread other than a user interface thread (either a ThreadPool thread or a new Thread )
  • You transfer work to the ThreadPool background thread using Task.Run .

in the code above in firstButton_Click, is it ok to manage user interface elements after waiting for a job? Have I always returned to the user interface thread?

You will return to the UI thread until you explicitly indicate that your code does not return to its current context using ConfigureAwait(false)

+9
source share

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

Starting with .NET 4.5, CollectionChanged also automatically sent to the user interface stream, so you don't have to worry about that. In this case, you will need to access the user interface elements (for example, for example) from the user interface stream.

+2
source share

All Articles