异步/等待和任务
好吧,我想我已经理解了整个异步/等待的事情。每当你等待某些事情时,你正在运行的函数都会返回,从而允许当前线程在异步函数完成时执行其他操作。好处是你不会开始一个新的线程。异步/等待和任务
这并不难理解,因为它有点像Node.JS的工作原理,除了Node使用大量的回调来实现这一点。这是我无法理解优势的地方。
套接字类当前没有任何异步方法(与async/await一起使用)。我当然可以将一个套接字传递给流类,并在那里使用异步方法,但是这会给接受新套接字带来问题。
据我所知,有两种方法可以做到这一点。在这两种情况下,我都会在主线程的无限循环中接受新的套接字。在第一种情况下,我可以为我接受的每个套接字启动一个新任务,并在该任务中运行stream.ReceiveAsync。但是,不会等待实际阻止该任务,因为任务没有其他任何事情要做?这又会导致线程池中产生更多的线程,这再次不如在任务内使用同步方法更好?
我的第二个选择是将所有接受的套接字放入几个列表之一(每个线程一个列表),并在这些线程内部运行一个循环,为每个套接字运行awaiting stream.ReceiveAsync。这样,每当我遇到await,stream.ReceiveAsync并开始从所有其他套接字接收。
我想我真正的问题是,如果这比任何线程池更有效,并且在第一种情况下,如果真的会比仅使用APM方法更糟。我也知道你可以使用await/async将APM方法包装到函数中,但是我看到它的方式,仍然会遇到APM方法的“缺点”,并且会在async/await中额外支付状态机的开销。
这不难理解,因为它有点像Node.JS的工作原理,除了Node使用大量的回调来实现这一点。这是我无法理解优势的地方。
Node.js确实使用了回调函数,但它还有一个重要的方面,它可以简化这些回调:它们都被序列化到同一个线程。因此,当您在.NET中查看异步回调时,通常会处理多线程以及异步编程(EAP-style callbacks除外)。
使用回调的异步编程称为“继续传递风格”(CPS)。这是Node.js唯一真正的选择,但它是.NET上的众多选项之一。特别是,CPS代码可能会变得非常复杂且难以维护,因此引入了编译器转换,以便您可以编写“看起来正常”的代码,编译器会将它转换为CPS。
在这两种情况下,我都接受主线程上无限循环中的新套接字。
如果你正在编写一个服务器,那么是的,你会在某个地方反复接受新的客户端连接。此外,您应该连续读取每个连接的套接字,因此每个套接字也有一个循环。
在第一种情况下,我可以为每个我接受的套接字启动一个新任务,然后在该任务中运行stream.ReceiveAsync。
你不需要一个新的任务。这就是异步编程的重点。
我的第二个选择是把所有接受的套接字在几个列表中的一个(每线程一个列表),以及里面那些线程运行一个循环,等待运行的stream.ReceiveAsync每个插座。
我不确定为什么你需要多个线程或任何专用线程。
您对async
和await
的工作方式似乎有点困惑。我推荐按此顺序阅读my own introduction,MSDN overview,Task-Based Asynchronous Pattern guidance和async
FAQ。
我也知道,你可以用APM方法为使用的await /异步功能,但我看到它的方式,你仍然得到的APM方法“吃亏”,用状态机的异步/等待额外的开销。
我不确定你指的是什么缺点。状态机的开销非零,但在插座I/O方面可以忽略不计。
如果你正在寻找套接字I/O,你有几个选择。对于读取,您可以使用APM或APM或Async方法的“无限”循环来执行这些操作。或者,您可以使用Rx或TPL Dataflow将它们转换为类似流的抽象。
另一个选择是我几年前写的一个库,名为Nito.Async。它提供了EAP风格的(基于事件的)套接字,它可以处理所有的线程编组,所以你最终得到了像Node.js这样简单的东西。当然,像Node.js一样,这种简单性意味着它不会像比较复杂的解决方案那样成比例。
我在第一个例子中执行任务的原因是因为我没有办法,我知道,要等待一个socket.Accept()调用...这意味着我会运行一个ReadAsync循环与我所有的套接字,然后等待下一个套接字连接...所以要么我可以在他们自己的读循环任务中运行所有套接字...或者我可以把所有套接字放在一个列表中(每个线程一个,if任何线程)和那些列表在单独的线程(为性能),如果任何线程在所有。 我关于包装APM方法的观点是,我摆脱了没有APM的缺点,但增加了状态机的开销... – 2013-02-25 20:56:27
Accept可以像任何其他操作一样包装到Task中;包装'BeginAccept' /'EndAccept'或'AcceptAsync'。 – 2013-02-25 21:06:17
异步套接字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
可能在这里使用,但这不是必需的。
tl; dr ...你有什么编码问题吗? – 2013-02-25 10:19:27
查看http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx查看可以异步使用以有效方式等待套接字操作的可重用方法的示例。 – 2013-02-25 10:58:27
我应该补充一点,基于任务的异步模式是MS推荐的新模式。 http://msdn.microsoft.com/en-us/library/vstudio/hh873175.aspx – 2013-02-25 11:02:23