考虑下面的同步示例:
private void DownloadBigImage() { var url = "https://cosmos-magazine.imgix.net/file/spina/photo/14402/180322-Steve-Full.jpg"; new WebClient().DownloadFile(url,"image.jpg"); }
如何通过仅使用正常的同步方法DownloadBigImage而不使用Task.Run来实现异步版本,因为这将仅使用线程池中的线程进行等待 – 这只是浪费!
也不要使用已经异步的特殊方法!这就是这个问题的目的:如何在不依赖已经异步的方法的情况下自己制作它?所以,没有像这样的事情:
await new WebClient().DownloadFileTaskAsync(url,"image.jpg");
在这方面非常缺乏可用的示例和文档.我发现只有这个:
https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth
其中说:
The call to GetStringAsync() calls through lower-level .NET libraries (perhaps calling other async methods) until it reaches a P/Invoke interop call into a native networking library. The native library may subsequently call into a System API call (such as write() to a socket on Linux). A task object will be created at the native/managed boundary,possibly using TaskCompletionSource. The task object will be passed up through the layers,possibly operated on or directly returned,eventually returned to the initial caller.
基本上我必须使用“P / Invoke互操作调用到本机网络库”……但是如何?
解决方法
从根本上说,您不能使用任何同步的现有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(); }
如果你考虑等待你的运动目标作弊,你需要使用回调来写这个.这太糟糕了,所以我只想写连接部分:
var s = new Socket(...) s.BeginConnect(...,ar => { //perform next steps here },null);
同样,这段代码非常原始,但它显示了原理.您可以注册在IO完成时调用的回调,而不是等待IO完成(这隐式发生在Connect中).这样你的主线程继续运行.这会把你的代码变成意大利面条.
您需要使用回调编写安全处理.这是一个问题,因为异常处理不能跨越回调.此外,如果您不想依赖框架来执行此操作,则可能需要编写读取循环.异步循环可以是弯曲的.