Raising PropertyChanged in asynchronous Task and UI Thread - c#

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.

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 new Thread)
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)

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.

Related

Changing SynchronizationContext Within Async Method

I am trying to post a lean question here, without code, because my question is so specific: Is it possible/acceptable to modify the SynchronizationContext within an Async method? If I don't set SynchronizationContext when the Async method begins, it seems that code within it--including events I raise and methods within the same class module I call--run on the same worker thread. However, when it comes time to interact with the UI, I find that the SynchronizationContext has to be set to the UI thread.
Is it okay to keep the SynchronizationContext set to the worker thread until such time that I want to invoke a call to a UI-based function?
EDIT:
To further clarify my above question, I find myself in a no-win situation with respect to the SynchronizationContext setting. If don't set the SynchronizationContext then my async operations run on a separate thread (as desired) but then I can't return data to the UI thread without encountering a cross-thread operation exception; if I set the SynchronizationContext to my UI thread then the operations I want to run on a separate thread end up running on the UI thread--and then (of course) I avoid the cross-thread exception and everything works. Clearly, I'm missing something.
If you care to read more, I've tried to provide a very clear explanation of what I'm trying to do and I realize you're investing your time to understand so thank you for that!
What I'm trying to do is shown in this flow diagram:
I have a Winforms application running on the UI thread (in black); I have a Socket object I'd like to run on its own thread. The job of the socket class is to read data from a communication socket and raise events back to the UI whenever data arrives.
Note that I start a message loop from the UI thread so that the socket is continually polled for data on its own thread; if data is received, I want to process that data synchronously on the same non-UI thread before grabbing any more data from the socket. (Yes, if something goes awry with any particular socket read, the socket might be left with unread data in it.)
The code to start the message loop looks like this:
if (Socket.IsConnected)
{
SetUpEventListeners();
// IMPORTANT: next line seems to be required in order to avoid a cross-thread error
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
Socket.StartMessageLoopAsync();
}
When I start the message loop from the UI thread, I call an async method on the Socket object:
public async void StartMessageLoopAsync()
{
while (true)
{
// read socket data asynchronously and populate the global DataBuffer
await ReadDataAsync();
if (DataBuffer.Count == 0)
{
OnDataReceived();
}
}
}
The Socket object also has the OnDataReceived() method defined as:
protected void OnDataReceived()
{
var dataEventArgs = new DataEventArgs();
dataEventArgs.DataBuffer = DataBuffer;
// *** cross-thread risk here!
DataReceived?.Invoke(this, dataEventArgs);
}
I have highlighted two areas of the diagram with "1" and "2" blue stars.
In "1" (on the diagram), I am using the async/await pattern. I am using a third-party socket tool that doesn't support Async, so I have wrapped that in my own ReadDataAsync() function that looks like this:
public override async Task ReadDataAsync()
{
// read a data asynchronously
var task = Task.Run(() => ReadData());
await task;
}
ReadData() wraps the third-party component's read method: it populates a global data buffer, so no return value is needed.
In "2" in the diagram, I am encountering the cross-thread risk described in my OnDataReceived() method cited above.
Bottom Line: If I set SynchronizationContext as shown in my first code clip above, then everything in the Socket object runs on its own thread until I try to invoke the DataReceived event handler; if I comment-out the SynchronizationContext then the only part of the code that runs on its own thread is the brief third-party socket-read operation wrapped in my DataReadAsync() method.
So my thought was whether I can set the SynchronizationContext just ahead of trying to invoke the DataReceived event handler. And even if I "can", the better question is whether this is a good idea. If I do modify SynchronizationContext within the non-UI thread then I'd have to set it back to its original value after invoking the DataReceived method, and that has a Durian-like code-smell to me.
Is there an elegant tweak to my design or does it need an overhaul? My goal is to have all red items in the diagram running on a non-UI thread and the black items running on the UI thread. "2" is the point at which the non-UI thread my cross over to the UI thread...
Thank you.
Setting context directly isn't the best idea since other functionality being occasionally executed in this thread can be affected. The most natural way to control synchronization context for async/await flows is using ConfigureAwait. So in your case I see two options to achieve what you want:
1) Use ConfigureAwait(false) with ReadDataAsync
public async void StartMessageLoopAsync()
{
while (true)
{
// read socket data asynchronously and populate the global DataBuffer
await ReadDataAsync().ConfigureAwait(false);
if (DataBuffer.Count == 0)
{
OnDataReceived();
}
}
}
which will make resuming everything after await in background thread. And then use Dispatcher Invoke to marshal DataReceived?.Invoke into UI thread:
protected void OnDataReceived()
{
var dataEventArgs = new DataEventArgs();
dataEventArgs.DataBuffer = DataBuffer;
// *** cross-thread risk here!
Dispatcher.CurrentDispathcer.Invoke(() => { DataReceived?.Invoke(this, dataEventArgs ); });
}
Or 2) Make some logic decomposition like as follows:
public async void StartMessageLoopAsync()
{
while (true)
{
// read socket data asynchronously and populate the global DataBuffer
await ProcessDataAsync();
// this is going to run in UI thread but there is only DataReceived invocation
if (DataBuffer.Count == 0)
{
OnDataReceived();
}
}
}
OnDataReceived is thin now and does only event triggering
protected void OnDataReceived()
{
// *** cross-thread risk here!
DataReceived?.Invoke(this, dataEventArgs);
}
This consolidates the functionality supposed to run in background thread
private async Task ProcessDataAsync()
{
await ReadDataAsync().ConfigureAwait(false);
// this is going to run in background thread
var dataEventArgs = new DataEventArgs();
dataEventArgs.DataBuffer = DataBuffer;
}
public override async Task ReadDataAsync()
{
// read a data asynchronously
var task = Task.Run(() => ReadData());
await task;
}
This seems like a scenario better served with reactive extensions:
Reactive Extensions for .NET

Await suspends the execution of the thread where it is used, Then how it will update the UI and improves performance

I know when we call an async method it frees the UI thread and suspends the execution of the thread when it encounters the await keyword.
Now suppose we are returning somevalue from the awaited method like:-
public async Task SomeEvent()
{
x= await Sum(2, 5); //Returns some output value
TextBox.Text = x; // updates the result in the UI
}
Now as per my understanding, since the UI thread returns back to the method which called this "SomeEvent" when it encounters the await keyword and It is necessary to perform all the UI related operations on the main thread.
Now in the above program, UI is busy in executing some other operations as it is freed from executing the above method and when asynchronously the execution of the awaited process completes then how the UI will get updated.
There is such thing as SynchronizationContext. Simply saying, the thread that completed some async work will wait (will be put into a queue) for the UI context to be free again, to run in it. It looks like this happens instantly, but actually it does not.

How to call viewModel method which updates binding data from worker thread?

I have a worker thread which calculates data for DataGrid after every change user have made. In some cases user make changes too fast so on GUI thread i call
Thread.Abort();
in the meantime on the worker thread i use such a construction
while (true)
{
try
{
_calculateEvent.WaitOne();
...
Application.Current.MainWindow.Dispatcher.BeginInvoke((Action)delegate()
{
_viewModel.UpdateInterfaceFromAssigningInfo(assigningInfo);
});
}
catch (ThreadAbortException)
{
Thread.ResetAbort();
}
}
Don't know if it will work at all, but for now my main problem is i can't call code on the GUI thread to update interface. At Invoke row i have exception: InvalidOperationException with message
The calling thread cannot access this object because a different
thread owns it.
I'm usually use slightly different way:
Application.Current.MainWindow.Dispatcher.BeginInvoke(new Action( ()=>
{
_viewModel.UpdateInterfaceFromAssigningInfo(assigningInfo);
}));
Try, may be it's a reason.
After some researches i have found info that accurately fills my needs, because in my case i need to update UI only after all calculation in Task have been done.
Option 1.
For the case of WPF application we can benefit from synchronization context task scheduler which runs task right on GUI thread. So, one can employ such scenario to update GUI after task has finished:
Task t = Task.Run(() => foo());
t.ContinueWith((task) =>
{
// Update observable properties
}, TaskScheduler.FromCurrentSynchronizationContext());
Continuation task will be executed on GUI thread and so will be able to update GUI.
Option 2
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
// Since we asynchronously wait, the UI thread is not blocked by our code.
await foo();
// Since we resume on the UI context, we can directly access UI elements.
UpdateObservableProperties();
}

Update UI Label from Task.Continuewith

I am working on a Winform Application. The Method is started by a BackgroundWorker Thread. I am sorry. I did not mention this earlier.
private void Method()
{
tasks[i] = Task.Factory
.StartNew(() => fileProcessor.ProcessEachMachine(mdetail))
.ContinueWith(UpdateLabel, TaskContinuationOptions.OnlyOnRanToCompletion);
}
I have a long running function ProcessEachMachine. In the continuation function UpdateLabel I want to access UIlabel and update the status.
private void UpdateLabel()
{
progressLbl.Text = "updated";
}
But the label is not getting updated. How to access UILabel and update the text of it.?
You have to set the TaskScheduler.FromCurrentSynchronizationContext on ContinueWith or else it will not be run in the UI context. Here is the MSDN on the override that you must use for this call to ContinueWith.
It should end up looking like this:
.ContinueWith(UpdateLabel, null,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext());
It may seem like nothing is happening, but the TPL is currently swallowing your cross thread exception. You should probably use the UnobservedTaskException if you are not going to inspect each result or check for its exception. Otherwise, when garbage collection occurs, the exception will happen then...which could create hard to debug errors.
UPDATE
Based on your update about the main Task being setup and started by a Backgroundworker, my main question is why this could not use a Task to start? In fact, if there is not more in the Method, then this is really just double work and might confuse other developers. You are already started asynchronously, so why not just do your work within the backgroundworker and use an OnComplete method that will UpdateLabel (as background workers are already context aware).
The main problem is still the same though, so here are some other solutions if you feel you must use the TPL:
You can Invoke back onto the main UI thread within the UpdateLabel method
You can pass the current context into the backgroundworker and use that instead
You can Wait for your original Task to return and then use the worker's oncomplete event to update the label.
Here is how I would do this (all pseudo code)
Background Worker Method:
Method() called because of Background worker
private void Method()
{
fileProcessor.ProcessEachMachine(mdetail);
}
Wire up background worker's OnRunWorkerCompleted:
if(!e.Cancelled && !e.Error)
UpdateLabel();
Task only method
Call Method() from the main thread and just let the TPL do its work :)
Task.Factory.StartNew(() => fileProcessor.ProcessEachMachine(mdetail))
.ContinueWith((precedingTask)=>{if(!precedingTask.Error)UpdateLabel;},
null, TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext());

Is there a System.Threading.SynchronizationContext.Current.PostAsync()?

I can return anything back to the UI thread like this:
// when I am on the UI thread
var _UI = System.Threading.SynchronizationContext.Current;
// then on a background thread
_UI.Post((x) => { DoSomething(); }, null);
But, if I am on that background thread for to make it asynchrnous, then calling _UI.Post() will undo my efforts and makes the UI wait for the execution of DoSomething().
Of course _UI.PostAsync() is pseudo-code. But is there a way to execute on the UI thread but in an asynchronous way? Fundamentally, I realize I am exposing my lack of knowlege and understanding. Be merciful, please. I'm just asking ;)
SynchronizationContext.Post() will not block the background thread, as it asynchronously "posts" back to the UI thread (in this case).
The UI thread will only block for the duration of the DoSomething() call - but in this case, you're saying you must run that on the UI thread, in which case, it will tie up the UI thread.
The key here is to put most of your work on the background thread, and only call .Post(...) for operations that directly set the user interface. As such, they should be very fast operations, and never (noticably) block the UI.

Categories

Resources