So, I've trolled the Internet for a solution to the problem where you are forced to operate in a completely asynchronous fashion when using WCF in Silveright. And, I've found a lot of different answers. Of course, the answers all address how to make your WCF calls in a synchronous fashion, one by one - with the usual caveat that you must stay off the UI thread, or rest in infinite peace. I view these as half a solution, because most of the time, you are, well, interacting with a user. It's Silverlight, it's what you do.
So, I wanted a way to (relatively) get all my code in one place. The advantages of this are: (relative) increase in human readability of the code hopefully leading to higher degrees of maintainability and keeping "functional" state out of your class - by this, I mean you want to avoid putting temporary state in your classes simply because you have to break your code executing one logical task across several methods.
My first idea was to use aspects - but they don't exist in regular old C# (still the cleanest answer in my opinion). My second idea was to use a call/cc style of continuations to easily be able to move my code around on and off the UI thread, I thought, hey, C# has yield, why not full on continuations. Nope, not there, but, we *do* have closures - lambdas! So, I could pull off a continuation passing style that (relatively) achieves what I want, albeit limited, and uglier than I had hoped. Finally, I, being lazy, didn't want to try and decide all the time if I was on the UI thread and what to do if I was, or for that matter how to get back to the UI thread when I was done playing with WCF. OK, enough context and blathering on about my approach and why.
Here's the class that will allow you to setup synchronous calls:
public class WcfSync
{
public static Dispatcher currentDispatcher = Application.Current.RootVisual.Dispatcher;
public void Sync(Action wcfCalls, Action uiCalls = null)
{
if (currentDispatcher.CheckAccess())
{
Thread t = new Thread(new ThreadStart(() => {
wcfCalls();
if (uiCalls != null)
{
currentDispatcher.BeginInvoke(uiCalls);
}
if (HandleSyncEnd != null)
{
HandleSyncEnd();
}
}));
t.Start();
}
else
{
wcfCalls();
if (uiCalls != null)
{
currentDispatcher.BeginInvoke(uiCalls);
}
if (HandleSyncEnd != null)
{
HandleSyncEnd();
}
}
}
public delegate void SyncEnd();
public event SyncEnd HandleSyncEnd;
}
Then, you can use most of the WCF synchronization techniques out there with Sync to handle the typical code scenario of do something with WCF and then do something on the UI to reflect the results. I used an interesting solution from
John Leitch[
^].
So, using John's sync wrapper, you get code that might look something like this:
WcfSync syncer = new WcfSync();
MmsLogin.MmsLoginClient loginClient = new MmsLogin.MmsLoginClient();
string username = usernameBox.Text;
string password = passwordBox.Password;
OKButton.IsEnabled = false;
string errorMessage = null;
syncer.Sync(
() =>
{
MmsLogin.LogonCompletedEventArgs tokenargs = null;
try
{
tokenargs =
Wrapper.SynchronousCall<MmsLogin.LogonCompletedEventArgs>(
loginClient,
"Logon",
username,
password );
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
if (tokenargs != null && tokenargs.Result.Length != 0)
{
token = tokenargs.Result;
}
},
() =>
{
if (token != null && errorMessage == null)
{
this.DialogResult = true;
}
else
{
if (errorMessage != null)
{
message.Content = errorMessage;
}
else
{
message.Content = "Username or password not accepted.";
}
message.Foreground = new SolidColorBrush(Colors.Red);
OKButton.IsEnabled = true;
}
}
);
One disadvantage is that Sync doesn't behave exactly the same way if you are on the UI thread as it does when you are off of it. If you are on the UI thread, Sync returns immediately. Otherwise, Sync waits until you are completed with the WCF section before returning... long story short, don't put code after your sync call that you want to have happen *after* the WCF and UI calls. Yup it's inelegant, and kludgy, but it accomplishes my goals and I can crank out reliable and maintainable code with it (provided I don't try to do more after Sync that I expect to behave synchronously following my Sync calls).
Enjoy,
Jerry