Hi Stoyan,
Thanks for the detailed response! You have some interesting ideas, but I
think you're assuming that I control the code for 'B'. In most cases my
class ("A" in your example) will be invoking async operations on some system
class and so I can't influence things like the dynamic type of the
IAsyncResult it returns to me. Your approach is relying on additional
coupling between A and B beyond the contract dictated by the async design
pattern. Even if I do control the code for B, I really don't want A to rely
on anything from B beyond what the async design pattern contract dictates
since increased coupling leads to less flexibility.
However, we can change your approach slightly to not require knowledge of
the 'special' AsyncResultWithState class. Instead of storing the 2nd state
object in my AsyncResult class, I can instead just store the inner
IAsyncResult which contains its own state which I can access via
IAsyncResult.AsyncState (see the pseudo-code below). To me this simplifies
things and makes the design more elegant.
In my mind, the thing that makes your design work is your approach of using
a WaitHandle to delay invoking the user callback until logic in BeginInvoke
has completed. That's an interesting idea, it certainly eliminates most of
the problems as the complexity arises mainly because we don't know which
will happen first. However, there are some disadvantages of doing that.
Personally, I'd rather not incur the overhead and added complexity of the OS
synchronization primitive. I don't really want to change the concurrency
model of the underlying class - just provide a wrapper that is as thin as
possible. It's certainly a very valid approach that is worth contrasting
with the other alternatives though.
Below is a sketch of the approach I'm currently using (all inside "class
A"). It's relatively simple (although its a non-trivial amount of code),
but it has the disadvantage that it creates two copies of the AsyncResult
object for the two different paths. I really don't have a sense of how bad
this is. It's not clear to me whether the async design pattern requires
that the two ways of getting the IAsyncResult (returned from Begin, and
passed into the callback) to return the same instance. I guess even if it
doesn't require this, it's a pretty natural assumption a client would make,
and therefore a bad thing to violate.
public IAsyncResult BeginFoo( SomeArg arg, AsyncCallback outerCallback,
object outerState )
{
DateTime someAdditionalState = DateTime.Now; // or whatever else we want
to track
FooState innerState = new FooState( someAdditionalState, outerCallback,
outerState );
System.AsyncCallback innerCallback = null;
if( outerCallback != null ) {
innerCallback = new AsyncCallback( _FooCallback );
}
IAsyncResult innerAsyncResult = _innerObj.BeginFoo( arg, innerCallback,
innerState );
FooAsyncResult outerAsyncResult =
new ChainedAsyncResult( innerAsyncResult, outerState );
return outerAsyncResult;
}
private void _FooCallback( System.IAsyncResult innerAsyncResult )
{
FooState innerState = (FooState) innerAsyncResult.AsyncState;
AsyncCallback outerCallback = innerState.OuterCallback;
// Create a new outer AsyncResult instance - is having two identical
instances a problem?
ChainedAsyncResult outerAsyncResult = new ChainedAsyncResult(
innerAsyncResult,
innerState.OuterState );
outerCallback( outerAsyncResult );
}
public void EndFoo( IAsyncResult outerAsyncResult )
{
ChainedAsyncResult chainedAsyncResult = (ChainedAsyncResult)
outerAsyncResult;
IAsyncResult innerAsyncResult = chainedAsyncResult.InnerAsyncResult;
FooState innerState = (FooState) innerAsyncResult.AsyncState;
_innerObj.EndFoo( innerAsyncResult );
// Possibly do additional stuff using innerState
}
private class ChainedAsyncResult : IAsyncResult
{
public ChainedAsyncResult(
System.IAsyncResult innerAsyncResult,
System.Object outerState )
{
_innerAsyncResult = innerAsyncResult;
_outerState = outerState;
}
// IAsyncResult interface
public object AsyncState { get { return _outerState; } }
public bool CompletedSynchronously { get { return
_innerAsyncResult.CompletedSynchronously; } }
public System.Threading.WaitHandle AsyncWaitHandle { get { return
_innerAsyncResult.AsyncWaitHandle; } }
public bool IsCompleted { get { return _innerAsyncResult.IsCompleted; } }
internal System.IAsyncResult InnerAsyncResult { get { return
_innerAsyncResult; } }
private System.IAsyncResult _innerAsyncResult;
private System.Object _outerState;
}
Thanks again!
Rick
----- Original Message -----
From: "Stoyan Damov" <[EMAIL PROTECTED]>
To: <[EMAIL PROTECTED]>
Sent: Friday, January 30, 2004 5:29 AM
Subject: Re: Elegant way to chain the async design pattern?
> I've drunk only one cofee, so what I'm about to tell you may be a complete
> crap:)
>
> What will happen if you just create your own class, which implements
> IAsyncResult.
> Let's name it AsyncResultWithState, and it will look like this (ignore
> syntax, I'm using Outlook):
>
> public class AsyncResultWithState : IAsyncResult
> {
> public AsyncResultWithState(object state)
> {
> // this is the user-supplied state, when the BeginFoo(...., state)
> was called
> this.state = state;
> }
> // IAsyncResult implementation is your job:)
> public object AdditionalState
> {
> get
> {
> return (additionalState);
> }
> get
> {
> additionalState = value;
> }
> }
> private object state, additionalState;
> }
>
> Now, let's say you have two classes, which implement the same async.
pattern
> for the Foo method.
> String Foo(string whatever);
> IAsyncResult BeginFoo(string whatever, AsyncCallback callback, object
> state);
> String EndFoo(IAsyncResult result);
>
> Now, in A's BeginFoo you want to delegate the call to B, e.g.
>
> Public IAsyncResult BeginFoo(string whatever, AsyncCallback callback,
object
> state)
> {
> IAsyncResult result = m_referenceToBacquiredAnyHow.BeginFoo(whatever,
> callback, state);
> CustomAsyncResult result = (CustomAsyncResult)result;
> // you can easily pass any state here
> result.AdditionalState = DateTime.Now; // for example
> return (result);
> }
>
> The only problem I see is that B's BeginFoo may finish too fast and
> "result.AdditionalState" may not be set for B to use it. However, you can
> prevent this from
> happening, by setting an event, on which B waits, e.g.
>
> // modify the CustomAsyncResult to include an
> AutoResetEvent AdditionaStateWasSet { get; }
>
> // in A's BeginFoo
>
> Public IAsyncResult BeginFoo(string whatever, AsyncCallback callback,
object
> state)
> {
> IAsyncResult result = m_referenceToBacquiredAnyHow.BeginFoo(whatever,
> callback, state);
> CustomAsyncResult result = (CustomAsyncResult)result;
> // you can easily pass any state here
> result.AdditionalState = DateTime.Now; // for example
> result.AdditionaStateWasSet.Set(); // signal the event
> return (result);
> }
>
> // in B.BeginFoo
>
> Public IAsyncResult BeginFoo(string whatever, AsyncCallback callback,
object
> state)
> {
> CustomAsyncResult result = CreateItSomehow();
> // launch a thread and pass it the result, so it can know when to
> start processing
> Thread t = new Thread(new ThreadStart(new Worker(result)).Run);
> t.Start();
> return (result);
> }
>
> // in the thread
> Public void Start()
> {
> AutoResetEvent evtStartWorking =
> this.theCustomAsyncResult.AdditionaStateWasSet;
> evtStartWorking.WaitOne(); // or whatever it was called:)
> }
>
> Do you get it?
> Or my brain is turning into piss?:)
>
> Cheers,
> Stoyan
>
> ===================================
> This list is hosted by DevelopMentor� http://www.develop.com
> Some .NET courses you may be interested in:
>
> NEW! Guerrilla ASP.NET, 26 Jan 2004, in Los Angeles
> http://www.develop.com/courses/gaspdotnetls
>
> View archives and manage your subscription(s) at
http://discuss.develop.com
>
===================================
This list is hosted by DevelopMentor� http://www.develop.com
Some .NET courses you may be interested in:
NEW! Guerrilla ASP.NET, 26 Jan 2004, in Los Angeles
http://www.develop.com/courses/gaspdotnetls
View archives and manage your subscription(s) at http://discuss.develop.com