Frankie wrote:
[...]
Unless I'm mistaken, the only reasonable way to get away from the client
generating the unique ID for the async operation - in the implementation at
the above links - would be for the CalculatePrimeAsync method (which
currently returns void) to return an IAsyncResult to the client. But that
would break the model being promoted here which hides IAsyncResult, etc and
other async operation implementation details from the client.
Okay...thanks for the links. I now understand better what design model
you're trying to follow.
First, I will point out that in the sample, it is very clear what the
answer to my repeated question regarding how the numeric ID is used. It
is used to retrieve an AsyncOperation from a HybridDictionary instance.
So the direct implication with respect to my previous comments is that
it's the AsyncOperation that you should pass back to the client, somehow
(note that you need not pass the client something it recognizes as an
AsyncOperation, or even something from which it can easily get the
AsyncOperation...it just needs to be something that immediately can be
translated into an AsyncOperation).
If you want to write code that generates unique numeric IDs, fine.
IMHO, GUIDs are overkill and random numbers aren't going to work at all
(since they aren't guaranteed to be unique). But otherwise, you
certainly could to that. The sample code you posted uses GUIDs, but
sequential numbers would work just fine (just be prepared to catch the
ArgumentException for non-unique numbers and try the next one, in the
unlikely even that you wrap around the full range of the 32-bit or
64-bit numeric variable you're using).
But, a couple of points that I hope will make clear what I'm trying to say:
1) It is not true that "the only reasonable way to get away from
the client generating the unique ID for the async operation...would be
for the CalculatePrimeAsync method...to return an IAsyncResult". The
method can return anything you want it to. It need not be an
IAsyncResult, and in fact should not be unless you really want a
mixed-mode event-plus-IAsyncResult design.
Using the sample code you're referring to as a template, let's look at
what modification I would make that would meet the goals I've already
stated. Rather than having a void return value, I would have
CalculatePrimeAsync return an instance of a class that looks something
like this:
class PrimeAsyncOperation : IPrimeAsyncOperation
{
AsyncOperation _ao;
public AsyncOperation AsyncOperation
{
get { return _ao; }
}
public PrimeAsyncOperation(AsyncOperation ao)
{
_ao = ao;
}
}
where:
public interface IPrimeAsyncOperation
{
}
The PrimeAsyncOperation class itself need not be visible to the client;
only the interface IPrimeAsyncOperation needs to be, and that's what the
CalculatePrimeAsync method would return.
Or rather, it's nice to have it that way; you could actually just return
an Object and forget about the interface altogether, but having a type
allows you to ensure in your client code that you maintain objects of
the right type. The interface isn't really required here; I just feel
it makes the code a little nicer.
Anyway, then any time the client wants to refer to the async operation,
rather than passing an ID which then needs to be used to look up the
AsyncOperation, all that the worker class has to do is retrieve the
AsyncOperation from the class.
One final note: the above still has one level of indirection. It's MUCH
more efficient than messing around with a hash table a la
HybridDictionary, but it's still a level of indirection. It's required
if you want to pass back a typed interface because AsyncOperation is
sealed and you don't have a way of instantiating the AsyncOperation
class except through the factory AsyncOperationManager (either situation
would force the issue, and you have both here)
But, if you are okay with passing back a plain old Object, then this
extra level of indirection can just go away. You'd just pass the
AsyncOperation instance itself back, as an Object. The client wouldn't
know anything about it except that it would use it as the way of
uniquely identifying the operation.
Alternatively, in a situation where the relevant class isn't a sealed
class with a factory for instantiation, you could create a new class
that inherits the actual identifying class, and have that new class
implement the empty interface that is public to the client. Then you
can have a typed reference passed back to the client, but which still
doesn't expose the internal aspects of the async implementation.
Note also that there's no requirement that you use AsyncOperation to
manage your asynchronous tasks. You could easily use some other
mechanism that does allow for inheriting the state object yourself.
2) IMHO, there is nothing invalid about having an event-based
design that returns something from the async method. Just because that
one MSDN sample doesn't, that doesn't mean it's prohibited. You can
design your class however you feel will work best for you. Heck, even
if you did want to return an IAsyncResult, you could, though it's true
that implies a certain level of non-event-based-ness that may not be
desirable.
The fact is, IMHO the sample code you're referring to is not a very nice
implementation of an event-based async design. I personally don't like
the aspect that a single class is responsible for managing multiple
asynchronous tasks. I prefer instead a design similar to the
BackgroundWorker where you instantiate a single class for each instance
of an asynchronous operation. Then, the class itself is all you need to
reference the asynchronous operation.
But, assuming you want a single class to manage multiple operations,
there's still no requirement that the client be the one to provide the
unique identifier, and IMHO it is much more natural and efficient to
allow the worker class managing the operations to provide the unique
identifier.
And I think that's all I'm going to say about that. :)
Pete