I agree, there should be something easier than your current approach. But until we can use T4 templates to generate the proxy classes, or unless you want to roll your own (not recommended IMO, YMMV), something along the lines of what you've already tried is your best shot, I think.
I'm currently using a hybrid of both approaches. I've wrapped all WCF calls inside a series of data access classes that translate the not-very-helpful event-driven model that MS provides into a much simpler callback approach. Within these data access classes, I've also wrapped each call with a static PreProcessCall() method that handles incrementing an outstanding call counter and marshaling the call onto a background thread; and I've wrapped each return call with a static PostProcessCall() method that decrements the outstanding call counter and marshals the callback onto the UI thread.
public static void PreProcessCall(Action action)
{
Logger.LogDebugMessage("Pre-processing call for {0}", (new StackTrace()).GetFrame(1).GetMethod().Name);
Interlocked.Increment(ref pendingCalls);
ThreadPool.QueueUserWorkItem(o =>
{
try
{
action();
}
catch (System.Exception ex)
{
DataPublisher.GetPublisher().RaiseDataProcessExceptionOccurred(ex, ex.Message);
UpdatePendingCalls();
}
});
}
private static void UpdatePendingCalls()
{
Interlocked.Decrement(ref pendingCalls);
Debug.Assert(pendingCalls >= 0, "The number of pending calls should never go below zero.");
if (pendingCalls <= 0)
{
lock (pendingCallNotifications)
{
while (pendingCallNotifications.Count > 0)
{
Action callback = pendingCallNotifications.Dequeue();
Globals.Dispatcher.BeginInvoke(callback);
}
}
}
}
public static void PostProcessCall(Action action)
{
Logger.LogDebugMessage("Post-processing call for {0}", (new StackTrace()).GetFrame(1).GetMethod().Name);
UpdatePendingCalls();
Globals.Dispatcher.BeginInvoke(() =>
{
try
{
action();
}
catch (System.Exception ex)
{
DataPublisher.GetPublisher().RaiseDataProcessExceptionOccurred(ex, ex.Message);
}
});
}
So a typical call looks something like this:
public void SendMessage(string message, OperationCallback callback)
{
DataConnectionManager.PreProcessCall(() =>
{
Logger.LogDebugMessage("SendChatMessage");
notificationClient.SendChatMessageAsync(roomViewModel.SessionId, message, callback);
});
}
void RoomService_SendChatMessageCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
OperationCallback callback = (OperationCallback)e.UserState;
DataConnectionManager.PostProcessCall(() =>
{
if (callback != null)
{
callback(e.Error);
}
Logger.LogDebugMessage("SendChatMessageCompleted.");
});
}
Like I said, basically what you've already tried.