89816

WPF/WCF Async Service Call and SynchronizationContext

Question:

I have a feeling there's got to be a better solution than what I've come up with; here's the problem:

A WPF form will call a WCF method, which returns a bool. The call itself should not be on the UI thread, and the result of the call will need to be displayed on the form, so the return should be marshaled back on to the UI thread.

In this example I created a "ServiceGateway" class, to which the form will pass a method to be executed upon completion of a Login operation. The gateway should invoke this Login-complete delegate using the UI SynchronizationContext, which is passed upon instantiation of the gateway from the form. The Login method invokes a call to _proxy.Login using an anon. async delegate, and then provides a callback which invokes the delegate ('callback' param) provided to the gateway (from the form) using the UI SynchronizationContext:

[CallbackBehavior(UseSynchronizationContext = false)] public class ChatServiceGateway : MessagingServiceCallback { private MessagingServiceClient _proxy; private SynchronizationContext _uiSyncContext; public ChatServiceGateway(SynchronizationContext UISyncContext) { _proxy = new MessagingServiceClient(new InstanceContext(this)); _proxy.Open(); _uiSyncContext = UISyncContext; } public void Login(String UserName, Action<bool> callback) { new Func<bool>(() => _proxy.Login(UserName)).BeginInvoke(delegate(IAsyncResult result) { bool LoginResult = ((Func<bool>)((AsyncResult)result).AsyncDelegate).EndInvoke(result); _uiSyncContext.Send(new SendOrPostCallback(obj => callback(LoginResult)), null); }, null); }

The Login method is called from the form in response to a button click event.

This works fine, but I have a suspicion I'm going about the Login method the wrong way; especially because I'll have to do the same for any other method call to the WCF service, and its ugly.

I would like to keep the async behavior and ui synchronization encapsulated in the gateway. Would it be better to have the asynchronous behavior implemented on the WCF side? Basically I'm interested if I can implement the above code more generically for other methods, or if there's a better way all together.

Answer1:

Provided that you're targeting at least VS 2012 and .NET 4.5, async/await is the way to go. Note the lack of SynchronizationContext reference - it's captured under the covers before the await, and posted back to once the async operation has completed.

public async Task Login(string userName, Action<bool> callback) { // The delegate passed to `Task.Run` is executed on a ThreadPool thread. bool loginResult = await Task.Run(() => _proxy.Login(userName)); // OR // await _proxy.LoginAsync(UserName); // if you have an async WCF contract. // The callback is executed on the thread which called Login. callback(loginResult); }

Task.Run is primarily used to push CPU-bound work to the thread pool, so the example above does abuse it somewhat, but if you don't want to rewrite the contract implemented by MessagingServiceClient to use asynchronous Task-based methods, it is still a pretty good way to go.

Or the .NET 4.0 way (no async/await support):

public Task Login(string userName, Action<bool> callback) { // The delegate passed to `Task.Factory.StartNew` // is executed on a ThreadPool thread. var task = Task.Factory.StartNew(() => _proxy.Login(userName)); // The callback is executed on the thread which called Login. var continuation = task.ContinueWith( t => callback(t.Result), TaskScheduler.FromCurrentSynchronizationContext() ); return continuation; }

This is a bit of a departure from the way you're currently doing things in that it is the responsibility of <em>the caller</em> to ensure that Login is getting called on the UI thread if they want the callback to be executed on it. That is, however, standard practice when it comes to async, and while you <em>could</em> keep a reference to the UI SynchronizationContext or TaskScheduler as part of your ChatServiceGateway to <em>force</em> the callback/continuation to execute on the right thread, it will blow out your implementation and personally (and this is just my <em>opinion</em>) I would say that that's a bit of a code smell.

Recommend

  • Why is a message in WCF seemingly always in SOAP format?
  • Issue with all WCF Callback clients faulting when one faults
  • Implementing “partial void” in VB
  • Sending HTML Form Data to Spring REST Web Service
  • Extract zip entries to another Zip file
  • Simulate click Geckofx vb,net
  • Groovy: Unexpected token “:”
  • How to have background script and something similar to a default popup?
  • d3 v4 drag and drop with TypeScript
  • jQuery show() function is not executed in Safari if submit handler returns true
  • RectangularRangeIndicator format like triangular using dojo
  • DotNetZip - Calculate final zip size before calling Save(stream)
  • Possible to stop flickering java tooltip in heavyweight mode?
  • How to redirect a user to a different server and include HTTP basic authentication credentials?
  • Can I make an Android app that runs a web view in Chrome 39?
  • How to model a transition system with SPIN
  • what is the difference between the asp.net mvc application and asp.net web application
  • using conditional logic : check if record exists; if it does, update it, if not, create it
  • Windows forms listbox.selecteditem displaying “System.Data.DataRowView” instead of actual value
  • Codeigniter doesn't let me update entry, because some fields must be unique
  • Getting error when using KSoap library to consume .NET web services
  • Cant find why the layout is getting smaller
  • How to stop GridView from loading again when I press back button?
  • LevelDB C iterator
  • Linking SubReports Without LinkChild/LinkMaster
  • apache spark aggregate function using min value
  • Bitwise OR returns boolean when one of operands is nil
  • EntityFramework adding new object to nested object collection
  • sending mail using smtp is too slow
  • Checking variable from a different class in C#
  • Sorting a 2D array using the second column C++
  • costura.fody for a dll that references another dll
  • Binding checkboxes to object values in AngularJs
  • Observable and ngFor in Angular 2
  • How to Embed XSL into XML
  • failed to connect to specific WiFi in android programmatically
  • UserPrincipal.Current returns apppool on IIS
  • Conditional In-Line CSS for IE and Others?
  • java string with new operator and a literal
  • How can I use threading to 'tick' a timer to be accessed by other threads?