简体   繁体   中英

AsyncCallback to BackgroundWorker

I want to use the .NET-FTP Libary ( http://netftp.codeplex.com ). The libary offers BeginOpenRead(string,AsyncCallback,object) to download the contents using the Asynchronous Programming Model. My implementation of the Callback is basically the same as the example:

static void BeginOpenReadCallback(IAsyncResult ar) {
        FtpClient conn = ar.AsyncState as FtpClient;

        try {
            if (conn == null)
                throw new InvalidOperationException("The FtpControlConnection object is null!");

            using (Stream istream = conn.EndOpenRead(ar)) {
                byte[] buf = new byte[8192];

                try {
                    DateTime start = DateTime.Now;

                    while (istream.Read(buf, 0, buf.Length) > 0) {
                        double perc = 0;

                        if (istream.Length > 0)
                            perc = (double)istream.Position / (double)istream.Length;

                        Console.Write("\rTransferring: {0}/{1} {2}/s {3:p}         ",
                                      istream.Position.FormatBytes(),
                                      istream.Length.FormatBytes(),
                                      (istream.Position / DateTime.Now.Subtract(start).TotalSeconds).FormatBytes(),
                                      perc);
                    }
                }
                finally {
                    Console.WriteLine();
                    istream.Close();
                }
            }
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        finally {
            m_reset.Set();
        }
    }

After the work of the async Method is completed, it would be great if a Completed event is fired (by the thread that started the asynchronous method in order to get no problems with the UI) to pass the results to the Main-Thread. Just like BackgroundWorker does (using RunWorkerCompleted).

How can I realize this?

Try converting the APM pattern into the TAP pattern ( more info ):

static public Task<Stream> OpenReadAsync(FtpClient ftpClient, string url)
{
    return Task.Factory.FromAsync(
         (asyncCallback, state) =>
             ftpClient.BeginOpenRead(url, asyncCallback, state),
         (asyncResult) =>
             ftpClient.EndOpenRead((asyncResult));
}

Then you can use async/await and don't have to worry about synchronization context:

Stream istream = await OpenReadAsync(ftpClient, url); 

Further, you can use Stream.ReadAsync :

while (await istream.ReadAsync(buf, 0, buf.Length) > 0) 
{
    // ...
}

BackgroundWorker is superseded by Task-based API, so it may be a win-win situation (more info: Task.Run vs BackgroundWorker and here ).

[UPDATE] If you work in VS2012+, you can target .NET 4.0 with Microsoft.Bcl.Async and still use the modern language and TPL features like async/await . I've been through that and I'd highly recommend it as it makes the future porting to .NET 4.5 a breeze.

Otherwise, you can use Task.ContinueWith(callback, TaskScheduler.FromCurrentSynchronizationContext()) to continue on the UI thread. Here's a related example .

The simplest way is pass the SynchronizationContext in to the BeginOpenRead and use that in the callback.

private class StateHolder
{
    public StateHolder(FtpClient client, SynchronizationContext context)
    {
        Client = client;
        Context = context;

        //SynchronizationContext.Current can return null, this creates a new context that posts to the Thread Pool if called.
        if(Context == null)
            Context = new SynchronizationContext();
    }

    public FtpClient Client {get; private set;}
    public SynchronizationContext Context {get; private set;}
}

//...

ftpClient.BeginOpenRead(someString,BeginOpenReadCallback, new StateHolder(ftpClient, SynchronizationContext.Current));

Then in your callback use that state object you passed in.

void BeginOpenReadCallback(IAsyncResult ar) 
{
    StateHolder state = ar.AsyncState as StateHolder;
    FtpClient conn = state.client;

    //... Everything else the same in the function.

    //state.Context can't be null because we set it in the constructor.
    state.Context.Post(OnCompleted, conn);

}

protected virtual void OnCompleted(object state) //I use object instead of FtpClient to make the "state.Context.Post(OnCompleted, conn);" call simpler.
{
    var conn = state as FtpClient;
    var tmp = Completed; //This is the event people subscribed to.
    (tmp != null)
    {
        tmp(this, new CompletedArgs(conn)); //Assumes you followed the standard Event pattern and created the necessary classes.
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM