2013-02-25 265 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可能在这里使用,但这不是必需的。