2013-02-25 264 views
2

好吧,我想我已經理解了整個異步/等待的事情。每當你等待某些事情時,你正在運行的函數都會返回,從而允許當前線程在異步函數完成時執行其他操作。好處是你不會開始一個新的線程。異步/等待和任務

這並不難理解,因爲它有點像Node.JS的工作原理,除了Node使用大量的回調來實現這一點。這是我無法理解優勢的地方。

套接字類當前沒有任何異步方法(與async/await一起使用)。我當然可以將一個套接字傳遞給流類,並在那裏使用異步方法,但是這會給接受新套接字帶來問題。

據我所知,有兩種方法可以做到這一點。在這兩種情況下,我都會在主線程的無限循環中接受新的套接字。在第一種情況下,我可以爲我接受的每個套接字啓動一個新任務,並在該任務中運行stream.ReceiveAsync。但是,不會等待實際阻止該任務,因爲任務沒有其他任何事情要做?這又會導致線程池中產生更多的線程,這再次不如在任務內使用同步方法更好?

我的第二個選擇是將所有接受的套接字放入幾個列表之一(每個線程一個列表),並在這些線程內部運行一個循環,爲每個套接字運行awaiting stream.ReceiveAsync。這樣,每當我遇到await,stream.ReceiveAsync並開始從所有其他套接字接收。

我想我真正的問題是,如果這比任何線程池更有效,並且在第一種情況下,如果真的會比僅使用APM方法更糟。我也知道你可以使用await/async將APM方法包裝到函數中,但是我看到它的方式,仍然會遇到APM方法的「缺點」,並且會在async/await中額外支付狀態機的開銷。

+1

tl; dr ...你有什麼編碼問題嗎? – 2013-02-25 10:19:27

+1

查看http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx查看可以異步使用以有效方式等待套接字操作的可重用方法的示例。 – 2013-02-25 10:58:27

+0

我應該補充一點,基於任務的異步模式是MS推薦的新模式。 http://msdn.microsoft.com/en-us/library/vstudio/hh873175.aspx – 2013-02-25 11:02:23

回答

2

這不難理解,因爲它有點像Node.JS的工作原理,除了Node使用大量的回調來實現這一點。這是我無法理解優勢的地方。

Node.js確實使用了回調函數,但它還有一個重要的方面,它可以簡化這些回調:它們都被序列化到同一個線程。因此,當您在.NET中查看異步回調時,通常會處理多線程以及異步編程(EAP-style callbacks除外)。

使用回調的異步編程稱爲「繼續傳遞風格」(CPS)。這是Node.js唯一真正的選擇,但它是.NET上的衆多選項之一。特別是,CPS代碼可能會變得非常複雜且難以維護,因此引入了編譯器轉換,以便您可以編寫「看起來正常」的代碼,編譯器會將它轉換爲CPS。

在這兩種情況下,我都接受主線程上無限循環中的新套接字。

如果你正在編寫一個服務器,那麼是的,你會在某個地方反覆接受新的客戶端連接。此外,您應該連續讀取每個連接的套接字,因此每個套接字也有一個循環。

在第一種情況下,我可以爲每個我接受的套接字啓動一個新任務,然後在該任務中運行stream.ReceiveAsync。

你不需要一個新的任務。這就是異步編程的重點。

我的第二個選擇是把所有接受的套接字在幾個列表中的一個(每線程一個列表),以及裏面那些線程運行一個循環,等待運行的stream.ReceiveAsync每個插座。

我不確定爲什麼你需要多個線程或任何專用線程。

您對asyncawait的工作方式似乎有點困惑。我推薦按此順序閱讀my own introductionMSDN overview,Task-Based Asynchronous Pattern guidanceasync FAQ

我也知道,你可以用APM方法爲使用的await /異步功能,但我看到它的方式,你仍然得到的APM方法「吃虧」,用狀態機的異步/等待額外的開銷。

我不確定你指的是什麼缺點。狀態機的開銷非零,但在插座I/O方面可以忽略不計。


如果你正在尋找套接字I/O,你有幾個選擇。對於讀取,您可以使用APM或APMAsync方法的「無限」循環來執行這些操作。或者,您可以使用Rx或TPL Dataflow將它們轉換爲類似流的抽象。

另一個選擇是我幾年前寫的一個庫,名爲Nito.Async。它提供了EAP風格的(基於事件的)套接字,它可以處理所有的線程編組,所以你最終得到了像Node.js這樣簡單的東西。當然,像Node.js一樣,這種簡單性意味着它不會像比較複雜的解決方案那樣成比例。

+0

我在第一個例子中執行任務的原因是因爲我沒有辦法,我知道,要等待一個socket.Accept()調用...這意味着我會運行一個ReadAsync循環與我所有的套接字,然後等待下一個套接字連接...所以要麼我可以在他們自己的讀循環任務中運行所有套接字...或者我可以把所有套接字放在一個列表中(每個線程一個,if任何線程)和那些列表在單獨的線程(爲性能),如果任何線程在所有。 我關於包裝APM方法的觀點是,我擺脫了沒有APM的缺點,但增加了狀態機的開銷... – 2013-02-25 20:56:27

+0

Accept可以像任何其他操作一樣包裝到Task中;包裝'BeginAccept' /'EndAccept'或'AcceptAsync'。 – 2013-02-25 21:06:17

3

異步套接字API不在身邊Task[<T>]基礎,所以它不是從async/await直接使用 - 但你可以彌合很容易 - 例如(完全未經):

public class AsyncSocketWrapper : IDisposable 
{ 
    public void Dispose() 
    { 
     var tmp = socket; 
     socket = null; 
     if(tmp != null) tmp.Dispose(); 
    } 
    public AsyncSocketWrapper(Socket socket) 
    { 
     this.socket = socket; 
     args = new SocketAsyncEventArgs(); 
     args.Completed += args_Completed; 
    } 

    void args_Completed(object sender, SocketAsyncEventArgs e) 
    { 
     // might want to switch on e.LastOperation 
     var source = (TaskCompletionSource<int>)e.UserToken; 
     if (ShouldSetResult(source, args)) source.TrySetResult(args.BytesTransferred); 
    } 
    private Socket socket; 
    private readonly SocketAsyncEventArgs args; 
    public Task<int> ReceiveAsync(byte[] buffer, int offset, int count) 
    { 

     TaskCompletionSource<int> source = new TaskCompletionSource<int>(); 
     try 
     { 
      args.SetBuffer(buffer, offset, count); 
      args.UserToken = source; 
      if (!socket.ReceiveAsync(args)) 
      { 
       if (ShouldSetResult(source, args)) 
       { 
        return Task.FromResult(args.BytesTransferred); 
       } 
      } 
     } 
     catch (Exception ex) 
     { 
      source.TrySetException(ex); 
     } 
     return source.Task; 
    } 
    static bool ShouldSetResult<T>(TaskCompletionSource<T> source, SocketAsyncEventArgs args) 
    { 
     if (args.SocketError == SocketError.Success) return true; 
     var ex = new InvalidOperationException(args.SocketError.ToString()); 
     source.TrySetException(ex); 
     return false; 
    } 
} 

注意:你應該避免在一個循環中運行接收器 - 我建議讓每個套接字負責在接收數據時自行抽取數據。唯一需要循環的是定期掃描殭屍,因爲不是所有的套接字死亡都是可檢測的。

還要注意,原始異步套接字API是完全可用的,沒有Task[<T>] - 我廣泛使用它。雖然await可能在這裏使用,但這不是必需的。