2014-01-29 27 views
2

我想使用.NET-FTP Libary(http://netftp.codeplex.com)。該庫提供BeginOpenRead(string,AsyncCallback,object)以使用異步編程模型下載內容。我的回調實現基本相同的例子:AsyncCallback到BackgroundWorker

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(); 
     } 
    } 

異步方法的工作完成後,這將是巨大的,如果已完成的事件被觸發(通過在啓動異步方法的線程爲了沒有問題與用戶界面)將結果傳遞給主線程。就像BackgroundWorker一樣(使用RunWorkerCompleted)。

我該如何認識到這一點?

+2

您使用的是什麼版本的.NET?您的選項在版本3.5,4和4.5之間有所不同。 –

+0

您需要知道要調用哪個線程,現在您不知道,並且在發佈的代碼中沒有可以找到的地方。 BackgroundWorker通過在其RunWorkerAsync()方法中複製SynchronizationContext.Current來完成該操作,之後使用其Post()方法調用回來。你必須在這個庫中找到一個類似的地方,你可以製作副本。或者只是留給用戶界面來調用,它永遠不會有猜測如何正確執行的問題。 –

+0

我使用.NET v4.0 – 0xDEADBEEF

回答

2

嘗試APM模式轉換爲TAP模式(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)); 
} 

然後你可以使用async/await並且不必擔心同步上下文:

Stream istream = await OpenReadAsync(ftpClient, url); 

此外,您可以使用Stream.ReadAsync

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

BackgroundWorker是基於任務的API所取代,所以它可能是一個雙贏的局面(詳細信息:Task.Run vs BackgroundWorkerhere)。

[更新]如果您在VS2012 +工作,你可以針對.NET 4.0 Microsoft.Bcl.Async並仍然使用現代語言和TPL功能,如async/await。我已經經歷了這一點,我強烈推薦它,因爲它可以讓將來移植到.NET 4.5中輕而易舉。

否則,您可以使用Task.ContinueWith(callback, TaskScheduler.FromCurrentSynchronizationContext())在UI線程上繼續。這是一個related example

1

最簡單的方法是將SynchronizationContext傳入BeginOpenRead並在回調中使用它。

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)); 

然後在回調使用在通過了狀態對象

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. 
    } 
}