Reputation: 3431
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?
Upvotes: 2
Views: 708
Reputation: 61686
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.
Upvotes: 2
Reputation: 127563
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.
}
}
Upvotes: 1