我试图了解如何以及何时使用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");
}
如何仅通过使用普通的同步方法async
而不使用DownloadBigImage
来实现Task.Run
版本,因为这将使用线程池中的线程只为等待-那只是浪费!
也不要使用已经async
的特殊方法!这是这个问题的目的:如何在不依靠已经异步的方法的情况下自己实现?因此,否之类的东西:
await new WebClient().DownloadFileTaskAsync(url, "image.jpg");
在这方面非常缺乏可用的示例和文档。我发现只有这样: https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth 上面写着:
对GetStringAsync()的调用通过较低级别的.NET库(可能调用其他异步方法)进行,直到到达对本机网络库的P / Invoke互操作调用。本机库随后可以调用系统API调用(例如,对Linux上的套接字的write())。可能使用TaskCompletionSource在本机/托管边界创建一个任务对象。任务对象将在各个层中向上传递,可能会对其进行操作或直接返回,最终返回给初始调用者。
基本上,我必须使用“ P /向内部网络库调用互操作” ...但是如何?
答案 0 :(得分: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();
}
这很粗略地使您获得HTTP响应作为字符串。
您可以使用await
轻松地使它真正地异步。
using (var s = new Socket(...))
{
await s.ConnectAsync(...);
await s.SendAsync(GetHttpRequestBytes());
var response = await new StreamReader(new NetworkStream(s)).ReadToEndAsync();
}
如果您考虑针对自己的运动目标await
作弊,则需要使用回调编写代码。这太糟糕了,所以我只想编写连接部分:
var s = new Socket(...)
s.BeginConnect(..., ar => {
//perform next steps here
}, null);
同样,此代码非常原始,但是显示了原理。您无需注册IO(在Connect
内部隐式发生),而是注册一个在IO完成时调用的回调。这样,您的主线程将继续运行。这会将您的代码变成意大利面条。
您需要使用回调编写安全的处理方式。这是一个问题,因为异常处理无法跨越回调。另外,如果您不想依赖框架来执行此操作,则可能需要编写一个读取循环。异步循环可能会让人弯腰。
答案 1 :(得分:1)
这是一个很好的问题,在大多数有关C#和异步的文章中,并没有很好地解释。
我搜索了很长时间,以为我可以并且应该实现自己的异步I / O方法。如果我使用的方法/库没有异步方法,我想我应该以某种方式将这些函数包装在使它们异步的代码中。事实证明,这对于大多数程序员而言实际上并不可行。是的,您可以使用Thread.Start(() => {...})
产生一个新线程,这确实使您的代码异步,但是它还会创建一个新线程,这对于异步操作来说是一笔昂贵的开销。它当然可以释放UI线程以确保您的应用程序保持响应状态,但不会像HttpClient.GetAsync()是真正的异步操作那样创建真正的异步操作。
这是因为.net库中的异步方法使用称为“ .NET中的标准P / Invoke异步I / O系统”的东西来调用底层OS代码,该代码在执行出站IO时不需要专用的CPU线程(网络或存储)。实际上,它并没有将线程专用于其工作,而是在完成工作后向.net运行时发出信号。
我对细节不熟悉,但是这种知识足以使我摆脱尝试实现异步I / O的注意力,使我专注于使用.net库中已经存在的异步方法(例如HttpClient.GetAsync( ))。可以找到更有趣的信息here (Microsoft async deep dive)和一个不错的description by Stephen Cleary here
答案 2 :(得分:-1)
创建一个执行同步代码的新任务。该任务将由线程池的线程执行。
private async Task DownloadBigImage()
{
await Task.Run(()=>
{
var url = "https://cosmos-magazine.imgix.net/file/spina/photo/14402/180322-Steve-Full.jpg";
new WebClient().DownloadFile(url, "image.jpg");
});
}