如何从头开始实现异步I/O绑定操作?

Igo*_*pov 9 c# asynchronous async-await

我试图了解如何以及何时使用async编程并进行I/O绑定操作,但我不理解它们.我想从头开始实现它们.我怎样才能做到这一点?

考虑下面的同步示例:

private void DownloadBigImage() {
    var url = "https://cosmos-magazine.imgix.net/file/spina/photo/14402/180322-Steve-Full.jpg";
    new WebClient().DownloadFile(url, "image.jpg");
}
Run Code Online (Sandbox Code Playgroud)

我如何async通过只使用正常的同步方法DownloadBigImage Task.Run实现该版本而不使用,因为这将仅使用线程池中的线程进行等待 - 这只是浪费!

不要使用已经有的特殊方法async!这就是这个问题的目的:如何在不依赖已经异步的方法的情况下自己制作它?所以,没有像这样的事情:

await new WebClient().DownloadFileTaskAsync(url, "image.jpg");
Run Code Online (Sandbox Code Playgroud)

在这方面非常缺乏可用的示例和文档.我发现只有这个:https: //docs.microsoft.com/en-us/dotnet/standard/async-in-depth ,其中说:

对GetStringAsync()的调用通过较低级别的.NET库(可能调用其他异步方法)调用,直到它到达本机网络库的P/Invoke互操作调用.本机库随后可以调用System API调用(例如对Linux上的套接字的write()).将在本机/托管边界创建任务对象,可能使用TaskCompletionSource.任务对象将通过层传递,可能在操作或直接返回,最终返回到初始调用者.

基本上我必须使用" P/Invoke互操作调用到本机网络库 "......但是如何?

usr*_*usr 8

我认为这是一个非常有趣的问题,也是一个有趣的学习练习.

从根本上说,您不能使用任何同步的现有API.一旦它同步,就无法将其变为真正的异步.您正确地识别出它Task.Run并且它的等价物不是解决方案.

如果拒绝调用任何异步.NET API,则需要使用PInvoke调用本机API.这意味着您需要调用WinHTTP API或直接使用套接字.这是可能的,但我没有经验来指导你.

相反,您可以使用异步托管套接字来实现异步HTTP下载.

从同步代码开始(这是一个原始草图):

using (var s = new Socket(...))
{
 s.Connect(...);
 s.Send(GetHttpRequestBytes());
 var response = new StreamReader(new NetworkStream(s)).ReadToEnd();
}
Run Code Online (Sandbox Code Playgroud)

这非常粗略地将您的HTTP响应作为字符串.

您可以通过使用轻松实现真正的异步await.

using (var s = new Socket(...))
{
 await s.ConnectAsync(...);
 await s.SendAsync(GetHttpRequestBytes());
 var response = await new StreamReader(new NetworkStream(s)).ReadToEndAsync();
}
Run Code Online (Sandbox Code Playgroud)

如果你考虑await就你的运动目标作弊,你需要使用回调来写这个.这太糟糕了,所以我只想写连接部分:

var s = new Socket(...)
s.BeginConnect(..., ar => {
   //perform next steps here
}, null);
Run Code Online (Sandbox Code Playgroud)

同样,这段代码非常原始,但它显示了原理.Connect您可以注册IO完成时调用的回调,而不是等待IO完成(这隐式发生在内部).这样你的主线程继续运行.这会把你的代码变成意大利面条.

您需要使用回调编写安全处理.这是一个问题,因为异常处理不能跨越回调.此外,如果您不想依赖框架来执行此操作,则可能需要编写读取循环.异步循环可以是弯曲的.


Tor*_*rfi 8

这是一个很好的问题,在大多数关于 C# 和 async 的文本中并没有很好地解释。

我搜索了很长时间,认为我可以而且应该实现我自己的异步 I/O 方法。如果我使用的方法/库没有异步方法,我想我应该以某种方式将这些函数包装在使它们异步的代码中。事实证明,这对于大多数程序员来说并不是真正可行的。是的,您可以使用生成一个新线程Thread.Start(() => {...}),这确实使您的代码异步,但它也会创建一个新线程,这对于异步操作来说是一个昂贵的开销。它当然可以释放您的 UI 线程以确保您的应用程序保持响应,但它不会像 HttpClient.GetAsync() 是真正的异步操作那样创建真正的异步操作。

这是因为 .net 库中的异步方法使用称为“.NET 中的标准 P/Invoke 异步 I/O 系统”的东西来调用在执行出站 IO(网络或存储)时不需要专用 CPU 线程的低级 OS 代码)。它实际上并没有将线程专用于其工作,而是在完成其工作后向 .net 运行时发出信号。

我不熟悉细节,但这些知识足以让我免于尝试实现异步 I/O,并使我专注于使用 .net 库中已经存在的异步方法(例如 HttpClient.GetAsync())。可以在此处找到更多有趣的信息(Microsoft 异步深入研究)以及Stephen Cleary的精彩描述此处

  • 你写的应该是 async/await 书籍/教程的介绍。由于文档非常缺乏,我也误解了事情并以错误的方式做了。谢谢你! (2认同)