I am implementing an asynchronous command pattern for the "client" class in a client/server application. I have done some socket coding in the past and I like the new Async pattern that they used in the Socket / SocketAsyncEventArgs classes.
My async method looks like this: public bool ExecuteAsync(Command cmd);
It returns true if the execution is pending and false if it completed synchronously. My question is: Should I always call the callback (cmd.OnCompleted), even in the event of an exception? Or should I throw exceptions right from ExecuteAsync?
Here are some more details if you need them. This is similar to using SocketAsyncEventArgs, but instead of SocketAsyncEventArgs my class is called SomeCmd.
SomeCmd cmd = new SomeCmd(23, 14, 10, "hike!");
cmd.OnCompleted += this.SomeCmd_OnCompleted;
this.ConnectionToServer.ExecuteAsync(cmd);
As with the Socket class, if you need to coordinate with your callback method (SomeCmd_OnCompleted in this case), you can use the return value of ExecuteAsync to know if the operation is pending (true) or if the operation completed synchronously.
SomeCmd cmd = new SomeCmd(23, 14, 10, "hike!");
cmd.OnCompleted += this.SomeCmd_OnCompleted;
if( this.ConnectionToServer.ExecuteAsync(cmd) )
{
Monitor.Wait( this.WillBePulsedBy_SomeCmd_OnCompleted );
}
Here is a greatly simplified version of my base classes, but you can see how it works:
class Connection
{
public bool ExecuteAsync(Command cmd)
{
/// CONSIDER: If you don't catch every exception here
/// then every caller of this method must have 2 sets of
/// exception handling:
/// One in the handler of Command.OnCompleted and one where ExecuteAsync
/// is called.
try
{
/// Some possible exceptions here:
/// 1) remote is disposed. happens when the other side disconnects (WCF).
/// 2) I do something wrong in TrackCommand (a bug that I want to fix!)
this.TrackCommand(cmd);
remote.ServerExecuteAsync( cmd.GetRequest() );
return true;
}
catch(Exception ex)
{
/// Command completing synchronously.
cmd.Completed(ex, true);
return false;
}
}
/// <summary>This is what gets called by some magic when the server returns a response.</summary>
internal CommandExecuteReturn(CommandResponse response)
{
Command cmd = this.GetTrackedCommand(response.RequestId);
/// Command completing asynchronously.
cmd.Completed(response, false);
}
private IServer remote;
}
abstract class Command: EventArgs
{
internal void Completed(Exception ex, bool synchronously)
{
this.Exception = ex;
this.CompletedSynchronously = synchronously;
if( this.OnCompleted != null )
{
this.OnCompleted(this);
}
}
internal void Completed(CommandResponse response, bool synchronously)
{
this.Response = response;
this.Completed(response.ExceptionFromServer, synchronously)
}
public bool CompletedSynchronously{ get; private set; }
public event EventHandler<Command> OnCompleted;
public Exception Exception{ get; private set; }
internal protected abstract CommandRequest GetRequest();
}