【问题标题】:How to wrap event-driven code with callbacks?如何用回调包装事件驱动的代码?
【发布时间】:2012-06-15 08:32:35
【问题描述】:

我正在使用第三方套接字库,尽管这与我的问题有些无关。该库包含一个“Socket”类,其中包含“ConnectAsync”和“WriteAsync”等方法。调用这些方法之一后控制立即返回,并且套接字类引发事件以通知操作何时完成,例如“ConnectCompleted”和“WriteCompleted”。当接收到数据时,该类还会引发一个事件:“PacketArrived”。

在我的应用程序中,我想发送数据并等待回复。由于这可能发生在许多地方,因此将上述套接字类包装在某种帮手中是有意义的,它可以为我完成所有这些。我还希望它是异步的,因此 UI (WPF) 在连接/写入/等待回复期间不会冻结,因此辅助方法调用可能如下所示:-

SocketHelper.SendData(dataToSend, myCallback);

收到数据后调用“myCallback”。

作为线程新手,我不知道如何编写这个助手 - 不知何故,它需要将各种方法和事件串在一起,即打开连接,等待连接完成,写入数据,等待写入完成,然后等待数据包到达(然后调用提供的回调)。任何帮助表示赞赏。

【问题讨论】:

  • 首先阅读关于 c# 事件和委托的入门书akadia.com/services/dotnet_delegates_and_events.html 我认为包装库可能是一座过分的桥梁,您不需要使用 .NET 事件机制来传递回调。
  • 哪个框架版本? .NET 4 具有任务并行库来帮助解决此类问题
  • @PanagiotisKanavos 它是 .Net 4
  • @JeffWatkins 在我的应用程序中有多个地方会发送数据,所以我不想每次都处理大量的套接字事件。将 connect-send-receive 功能封装在单个方法中似乎是一种干净的方法,通过回调表示它已接收到数据以在方法执行时保持 UI 响应。

标签: c# multithreading asynchronous


【解决方案1】:

更新 - 2021 年 11 月 1 日

大多数原始技术在 .NET Framework 4.7 及更高版本以及 .NET Core(包括 .NET 5 及更高版本)中作为开箱即用的方法提供。目前 .NET 6 已在生产中得到支持,目前对 .NET 的长期支持,两周后即可获得完整的 RTM。

await socket.ConnectAsync(someIp,somePort);
await socket.Send(msg, SocketFlags.None);

类支持通过可选的 CancellationToken 参数取消。

  • 对于基于 Socket 的高性能代码,Pipes were introduced 允许通过重用从一个处理步骤传递到下一个处理步骤的内存缓冲区,以最少的分配处理任意消息大小。这节省了内存在缓冲区之间复制数据所需的时间。

  • WebClient 完全被 HttpClient 取代。事实上,WebClient 和 HttpWebRequest 类现在只是 HttpClient 的兼容性包装器。 HttpClient 是线程安全和异步的,使用套接字和管道提供比 WebClient 更好的性能:

HttpClient _client=new HttpClient(...);

...
async Task<string> GetThatPage(string someUrl)
{
   var page=await _client.GetStringAsync(someUrl);
   ....
}

在 .NET 6 中,Parallel.ForEachAsync 使得同时发出多个异步请求变得微不足道:

var urls=new List<string>();

...
await Parallel.ForEach(urls,async url=>{
    var page=_client.GetStringAsync(url);
    var fileName=CalculateNameFrom(url);
    await File.WrileAllTextAsync(fileName,page);
});

.NET (Core) 中的默认编码是 UTF8。

var reply=await ping.SendPingAsync(someIP);

原始答案 (2012)

Stephen Toub 在“Awaiting Socket Operations”中讨论了 .NET 4.5 的这个主题。通过一些工作,您可以对 .NET 4 使用相同的技术。

您可以将任务并行库用于simplify asynchronous calls,而不是使用回调。使用 TPL,您可以将所有套接字操作和回调转换为调用操作并处理结果的任务。

使用 ContinueWith 等方法可以非常轻松地组合任务,以便仅在第一个任务完成时执行链中的下一个任务。

使用线程池中的线程执行任务,因此您不必担心线程。

TPL 已经提供了一种从 BeginXXX/EndXXX 函数对或 IAsyncResult 对象创建任务的方法。如果您的套接字库提供了这些,您可以立即开始使用任务。

要处理事件,您可以使用任务并行库中的 TaskCompletionSource 创建一个任务,该任务将在您调用套接字方法时启动,并仅在引发相应事件时完成。该技术在“Tasks and the Event-based Asynchronous Pattern”中有所描述

ParallelExtensionsExtras 库使用这种技术为WebClient, SmtpClient and Ping 提供异步方法版本。

使用“任务和 EAP”中的代码,您可以为您的 Socket 类编写扩展方法,如下所示:

public static Task ConnectTask( this Socket socket, object address) 
{ 
    var tcs = CreateSource(address); 
    socket.ConnectCompleted += 
        (sender, e) => TransferCompletion(tcs, e, () => e.Result, null); 
    socket.ConnectAsync(address, tcs); 
    return tcs.Task; 
}

并像这样使用它:

var connectTask=mySocket.ConnectTask(myaddress);
connectTask.ContinueWith(t=> { ... });

在 .NET 4.5 中事情变得更加容易。 async/await 关键字允许您取消 ContinueWith 调用并编写看起来与其同步版本非常相似的代码。您可以检查“Awaiting Socket Operations”以获得专门针对 Sockets 的扩展。

【讨论】:

  • TaskCompletionSource 听起来很完美。套接字库只公开事件(没有 Begin/EndXxxx 方法)。非常感谢。
  • @panagiotis Kanavos 请更新您的链接,它们都不起作用
  • 此答案中的链接已损坏,因此无用。
  • @Quade2002 几年前,微软重组了它的博客站点,破坏了每个人的链接。不过,在过去的 9 年里,情况发生了很大的变化。 Socket 现在有基于异步任务的方法,SmptClient 和 WebClient 已经过时了。
  • 我在寻找事件驱动的代码,而不是套接字代码。这就是标题的含义以及我正在寻找的示例,因此,那时它就没用了。 '为什么'事情改变并不重要,这里根本没有什么可以使用的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 2013-07-07
  • 1970-01-01
  • 2018-05-11
  • 1970-01-01
相关资源
最近更新 更多