调用异步方法与事件的区别

6

我正在开发一个用于网络应用协议的客户端库。

客户端代码调用库进行初始化并连接到服务器。客户端可以向服务器发送请求,但服务器也可以向客户端发送请求(命令,在下面称为Cmd)。

传输协议是TCP/IP,因此客户端库连接到服务器,并调用异步方法以从服务器检索下一个请求或响应,以避免在等待服务器响应/请求时发生I/O阻塞。

话虽如此,我在库中看到了两种可能的解决方案(仅使用C#构造和没有特定的第三方框架),以允许客户端从服务器接收请求:

要么在库中提供一个事件

public EventHandler<ReceivedCmdEventArgs> event ReceivedCmd;

客户端需要订阅相关通知,以便及时接收来自服务器的请求。为此,我需要在客户端库中创建一个异步循环来接收来自服务器的请求,并在接收到命令时触发事件。

另一种解决方案是在客户端库中创建一个相应的方法。

public async Task<Cmd> GetNextCmdAsync()

客户端代码将在异步循环中调用以接收命令。

这些解决方案是否有点相似?是完全使用C#5的async/await构造还是不再依赖事件更好?有什么区别?有任何建议或备注吗?

谢谢!

3个回答

4
我认为事件驱动的方法在您的情况下更好。
实际上,您谈论的是可观察者/观察者模式。如果接收到某个命令,未知数量的侦听器/观察者正在等待执行某些操作。
异步/等待模式不如事件驱动的方法有效,因为它会变成“我期望一个结果”,而不是“只要您报告我已收到命令,我就会按照您要求执行”。
从概念上讲,我更喜欢事件驱动的方法,因为它更符合您的架构目标。
C# 5中的异步/等待模式并非为您的情况设计,而是为当某些代码执行异步任务并且下一行代码应在任务收到结果后执行时使用。

2
阻塞线程,因为我猜我将要接收一个命令。async/await 不会阻塞线程。从概念上讲,这两种方法是相同的。 - Orestis P.
@OrestisP。我在试着回忆我为什么写下那句话。我想我当时说的是“像”而不是“是”。 - Matías Fidemraizer

4
Task代表单个异步操作,例如接收单个命令。因此,它不适用于事件流
流事件的终极库是Reactive Extensions (Rx),但它的学习曲线非常陡峭。
一个较新的选择是鲜为人知的TPL Dataflow,它允许构建友好的async数据流网格。实际上,我正在编写一个友好的TCP/IP套接字包装器,并将ISourceBlock<ArraySegment<byte>>作为读取流公开。最终用户可以直接从该块接收(使用ReceiveAsync),或者将其链接到自己的数据流中(例如,可以进行消息分帧、解析甚至处理)。
Dataflow比Rx略微低效,但我认为更低的学习曲线是值得的。
我不建议使用裸事件 - 你要么得到自由线程事件(想想如何处理套接字关闭 - 在处理后事件可能会发生吗?),要么得到基于事件的异步模式(它有自己的类似于同步到提供的SynchronizationContext的问题)。Rx和Dataflow都提供了更好的解决方案,以实现同步和处理/取消订阅。

但是也许他可以将引发命令事件与UI或主线程同步。 - Matías Fidemraizer
这就是基于事件的异步模式,它有自己的一套问题。例如,如果事件在调用Dispose时排队到UI线程会发生什么? - Stephen Cleary
很明显,为了使一切都线程安全,您需要考虑一些细节。每件事都有其利弊。如果像这样的事件正在工作,Dispose 不应该被有效地调用。 - Matías Fidemraizer

0

由于您正在制作库,事件似乎更合适。

事件允许您构建库而不强制要求指定回调函数。

您的库的使用者决定他们感兴趣的内容并监听这些事件。

另一方面,异步任务是在您知道会有延迟(IO,网络等)的情况下使用。异步任务允许您在这些延迟发生时释放资源,从而实现更好地利用资源。

异步任务不能替代您引发的事件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接