Introduction
Updating UI in both WinForms and WPF/Silverlight can only be performed from the UI thread. Any attempt to update visual controls from a background thread will fail with UnauthorizedAccessException. Also an attempt to update an object bound to visual control will fail, if it results in updating a visual control.Unfortunately scenarios where it's necessary to update UI from another thread are quite common. For example accessing a Web Service asynchronously relies on a callback methods which execute in the background. Note that in case of Silverlight, asynchronous is the only way to access a Web Service.
Example
Let's use Echo Web Service Demo at http://www.aspspider.info/burzyn/echoservice.svc as an example. Assuming that its service reference has been added with Generate Asynchronous operations option enabled, the following two methods are present in IEchoService interfaceIAsyncResult BeginDescription(System.AsyncCallback callback, object asyncState);
string EndDescription(System.IAsyncResult result);
They are equivalent to a synchronous function with the signature of:
string Description()
Typically asynchronous calls to a Web Service use callbacks to complete the operation, as illustrated below.
IEchoService echoService; void Init() { ... echoService.BeginDescription(DescriptionCallback, echoService); ... } void DescriptionCallback(IAsyncResult ar) { string description = (ar.AsyncState as IEchoService).EndDescription(ar); // this will fail with UnauthorizedAccessException lblDescription.Content = description; }
This fails because DescriptionCallback is trying to access UI while executing in a background thread.
Solution
In case of WPF/Silverlight, it can be resolved by using the System.Windows.Threading.Dispatcher namespace. Every control class has the Dispatcher property, which can be used to render changes from any thread. Using the Dispatcher property of the current Page, DescriptionCallback needs to be modified as follows.private delegate void DispatchHandler(string arg); private void UpdateDescription(string description) { lblDescription.Content = description; } void DescriptionCallback(IAsyncResult ar) { string description = (ar.AsyncState as IEchoService).EndDescription(ar); Dispatcher dispatcher = this.Dispatcher; DispatchHandler handler = new DispatchHandler(UpdateDescription); Object[] args = { description }; this.Dispatcher.BeginInvoke(handler, args); }
BeginInvoke takes two parameters - delegate to be executed in the UI thread and array of objects used as parameters of the delegate. Parameters are matched with object in the array by position. This is rather error prone solution - there's no type safety and changes to the number or type of the parameters might break the code. It can be done in much safer and clearer way by using an anonymous method.
private delegate void DispatchHandler(); echoService.BeginEchoString(txtString.Text, delegate(IAsyncResult ar) { Dispatcher dispatcher = this.Dispatcher; DispatchHandler handler = delegate() { lblDescription.Content = echoService.EndEchoString(ar); }; dispatcher.BeginInvoke(handler, null); }, null);
Note that it's also shorter and more readable. Of course, it can be even further shortened by nesting anonymous methods.
{ echoService.BeginDescription(delegate(IAsyncResult ar) { string description = echoService.EndDescription(ar); DispatchHandler handler = new DispatchHandler(delegate() { lblDescription.Content = description; }); this.Dispatcher.BeginInvoke(handler, null); } null); }
How about WinForms?
Although the WinForms controls don't have the Dispatcher property, they expose Invoke and BeginInvoke methods, which can be used in the same manner.
echoService.BeginEchoString(txtString.Text, delegate(IAsyncResult ar) { string description = echoService.EndDescription(ar); DispatchHandler handler = delegate() { lblDescription.Text = description; }; this.BeginInvoke(handler); }
Additionally WinForms controls expose InvokeRequired property to determine if a call in on a different thread than the control, essentially meaning if the control can be access directly. You can take advantage of the InvokeRequired property using the like below.
if (lblDescription.InvokeRequired) { DispatchHandler handler = delegate() { lblDescription.Text = description; }; this.BeginInvoke(handler); } else lblDescription.Text = description;
Summary
Although updating UI from background might look like an issue, but it's fairly easy to find the way around it. Using anonymous methods allows for clearer and less error prone code.
Quick note on Delphi. Within VCL, access to UI from background thread is not thread safe, but it's not blocked. It's up to a developer to resolve any potential problems. It's recommended to wrap any code executed in a background thread in the Synchronize procedure of the TThread class.
Quick note on Delphi. Within VCL, access to UI from background thread is not thread safe, but it's not blocked. It's up to a developer to resolve any potential problems. It's recommended to wrap any code executed in a background thread in the Synchronize procedure of the TThread class.
Hi Kris,
ReplyDeleteI need to to update the user interface in OnTimer event of a TTimer component. What would be easy way to do it safely?
Regards
Hi Fiona,
DeleteAs far as I know Delphi TTimer works like DispatchTimer in .NET, meaning that OnTimer event executes in the main thread. You don't need to worry as synchronisation is managed by the component.
Cheers
Thanks Kris,
DeleteIt looks like you are right. BTW. Quite interesrting amd helpful post.
Fiona
Hi Kris,
ReplyDeleteI have a wpf control method which adds a pushpin on the map. When I call the method from winforms the method execute but does not add the pin.
Hi Anup,
DeleteI gather, you're hosting WPF Control in WinForms and you you don't see expected result when you execute a method on the control.
Is it correct?
Kris