如何使用回调包装事件驱动的代码

本文关键字:事件驱动的 代码 包装 回调 何使用 | 更新日期: 2023-09-27 18:37:12

我正在使用第三方套接字库,尽管这与我的问题有些无关。该库由一个"Socket"类和"ConnectAsync"和"WriteAsync"等方法组成。在调用这些方法之一后,控件立即返回,套接字类会引发事件以在操作完成时发出通知,例如"连接已完成"和"写入完成"。该类还会在收到数据时引发一个事件:"数据包到达"。

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

SocketHelper.SendData(dataToSend, myCallback);

接收到数据时调用"myCallback"。

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

如何使用回调包装事件驱动的代码

更新 - 2021 年 11 月 1 日

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

  • 类为 Sockets 提供了基于任务的扩展方法,例如 ConnectAsync 允许异步打开连接,而 SendAsync 异步发送缓冲区:
await socket.ConnectAsync(someIp,somePort);
await socket.Send(msg, SocketFlags.None);

这些类支持通过可选的 CancelToken 参数取消。

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

  • 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(核心)中的默认编码为 UTF8。

  • SmptClient本身已经过时,其文档页面建议使用MailKit等开源客户端。

  • 异步 ping 可以使用 SendPingAsync 发送

var reply=await ping.SendPingAsync(someIP);
<小时 />

原答案 (2012)

Stephen Toub 在"等待套接字操作"中讨论了 .NET 4.5 的此主题。只需一些工作,您就可以对 .NET 4 使用相同的技术。

您可以使用任务并行库来简化异步调用,而不是使用回调。使用 TPL,您可以将所有套接字操作和回调转换为调用操作和处理结果的任务。

使用诸如 ContinueWith 之类的方法可以非常轻松地编写任务,以便仅在第一个任务完成时才执行链中的下一个任务。

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

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

若要处理事件,可以使用任务并行库中的 TaskCompletionSource 创建一个任务,该任务将在调用套接字方法时启动,并且仅在引发相应事件时完成。该技术在"任务和基于事件的异步模式"中进行了描述

ParallelExtensionsExtras 库使用此技术为 WebClient、SmtpClient 和 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 调用并编写看起来与其同步版本非常相似的代码。您可以检查"等待套接字操作"以获取专门针对套接字的扩展。