Passing a parameter to BeginOpenReadCallback

Mar 25, 2013 at 5:05 PM
Hello,

I am writing a program that connects to FTP account, retrieves all the file names, and downloads the files to a MS SQL Server database.

I have been studying the BeginOpenRead example, but I don't get it how I can pass a parameter to the BeginOpenReadCallback method - I need to pass it a placeholder object where to store the downloaded content. But the only parameter passed there is conn, as state, and perhaps I should not mess with that.

So, the question is how do I let the method that performs the download receive some extra parameters? Of course, I could use static variables, but that would break any simultaneousness. Or maybe I am doing this totally wrong and have to use some other approach to the problem?
Coordinator
Mar 25, 2013 at 5:44 PM
You can pass whatever you want into your state object, for example you could pass a struct or a class that has the client object along with other variables you want to make available to your AsyncCallback method. Another dirty solution would be to pass an object array, however the struct would be much nicer to read and extend down the road. Consider the following code:
       struct AsyncArguments {
            public FtpClient Client;
            public object Argument1;
            public object Argument2;
        }

        public void BeginOpenRead() {
             ....
                conn.BeginOpenRead("/path/to/file",
                    new AsyncCallback(BeginOpenReadCallback), new AsyncArguments() {
                        Client = conn
                    });
             ....
        }

       void BeginOpenReadCallback(IAsyncResult ar) {
            AsyncArguments args = (AsyncArguments)ar.AsyncState;
            ....
        }
Mar 25, 2013 at 7:35 PM
Yes, I already considered using the state object for my purposes, but I stumbled upon this line in the code of BeginOpenRead example:
using (Stream istream = conn.EndOpenRead(ar))
Ie, the stream object is retrieved by calling EndOpenRead method, and it has to be passed the same IAsyncResult object what was passed to BeginOpenReadCallback method. Which means that I can not pass my doctored state object to conn.EndOpenRead method - I am sure it expects that there is only connection object passed as the state object. Maybe there is a way how to synthesize IAsyncResult with connection object as state object, so that the conn.EndOpenRead method could be called with proper data in the parameter?
Coordinator
Mar 25, 2013 at 7:44 PM
I'm not sure I understand what the real problem is, the IAsyncResult object passed to your AsyncCallback method is the same IAsyncResult returned from the Begin* methods. Your connection object can be declared in a scope accessible by the AsyncCallback method or it can be passed in as part of an object that was used for the state parameter. This may be clearer:
   void BeginOpenReadCallback(IAsyncResult ar) {
            AsyncArguments args = (AsyncArguments)ar.AsyncState;

                using (Stream istream = args.Client.EndOpenRead(ar)) {
                    
                }
    }
Mar 25, 2013 at 8:24 PM
In your code, this line:

args.Client.EndOpenRead(ar)

calls the EndOpenRead method, passing the original ar for parameter. But this ar is the one whose state object is your special struct. Isn't it that the conn.EndOpenRead method expects that the state object packed in the ar object is only connection object? I mean, why the EndOpenRead has to be passed the IAsyncResult parameter at all, if that can be with whatever contents?
Coordinator
Mar 25, 2013 at 8:38 PM
IAsyncResult and the state object are not the same! The state object is a user defined value and nothing more that is included as a property in the IAsyncResult object passed to your AsyncCallback method. You're making a big deal out of nothing. You can put whatever you like in the state object otherwise it wouldn't be exposed at all. If you want to know more about how and why do some reading on the IAsyncResult pattern and asynchronous method invocation in .net. That's all this code is. BeginOpenRead() invokes the OpenRead() method asynchronously.
Coordinator
Mar 25, 2013 at 8:42 PM
Mar 25, 2013 at 9:17 PM
Thanks for this info. I had very strong assumption that the FtpClient.EndOpenRead method expects to see the connection object in the ar parameter's related state object. It turns out, unbased one. Apparently, the code of EndOpenRead doesn't expect that the state object is something special. I was mislead by the BeginOpenReadCallback, which is called by IAsyncResult parameter that indeed has the specific contents of the state object, the connection object.