Meh*_*ran 5 c# progress httpwebrequest
我试图上传和下载使用(在相同的请求)发送到服务器HttpWebRequest中C#,并由于数据的大小相当大(考虑网速),我想展示如何工作的远做的用户又有多少是离开(不是几秒钟,而是百分比).
我已经阅读了几个尝试实现这一点的例子,但没有一个显示任何进度条.它们都只是用来async在上传/下载时不阻止UI.而且他们主要关注上传/下载,没有人尝试将它们都包含在同一个请求中.
由于我使用.Net 4作为我的目标框架,我async自己无法实现一个方法.如果您要建议任何异步,请使用Begin...方法而不是await关键字!谢谢.
Owe*_*enP 34
你需要知道一些事情才能成功.
第0步:保持.NET 4.0的文档方便:
如果你查看HttpWebRequest文档,你会发现它GetResponse()有两个同名的方法:BeginGetResponse()和EndGetResponse().这些方法使用最古老的.NET异步模式,称为"IAsyncResult模式".有关此模式的数千页文本,如果您需要详细信息,可以阅读教程.这是速成课程:
Foo(),BeginFoo()可以使用参数并且必须返回IAsyncResult实现.此方法执行任务Foo()执行,但在不阻止当前线程的情况下执行工作.BeginFoo()采用AsyncCallback参数,它希望您提供一个它将在完成后调用的委托.(这是了解它已完成的几种方法之一,但恰好是技术HttpWebRequest用途.)EndFoo()将IAsyncResult返回的by BeginFoo()作为参数并返回相同的东西Foo()返回.接下来,您应该了解一个陷阱.控件具有一个名为"线程关联"的属性,这意味着如果您正在处理创建控件的线程,它只对与控件交互有效.这很重要,因为BeginFoo()不能保证从该线程调用您提供的回调方法.这实际上很容易处理:
InvokeRequired属性,它是从错误的线程调用的唯一安全属性.如果它返回true,你知道你在一个不安全的线程.Invoke()接受委托参数的方法,并将在创建控件的线程上调用该委托.完成所有这些后,让我们开始查看我在一个简单的WinForms应用程序中编写的一些代码,以报告下载Google主页的进度.这应该类似于解决您的问题的代码.它不是最好的代码,但它演示了这些概念.表单有一个名为的进度条progressBar1,我GetWebContent()点击了一个按钮.
HttpWebRequest _request;
IAsyncResult _responseAsyncResult;
private void GetWebContent() {
_request = WebRequest.Create("http://www.google.com") as HttpWebRequest;
_responseAsyncResult = _request.BeginGetResponse(ResponseCallback, null);
}
Run Code Online (Sandbox Code Playgroud)
此代码启动异步版本GetResponse().我们需要存储请求和IAsyncResultin字段,因为ResponseCallback()需要调用EndGetResponse().所有内容都在GetWebContent()UI线程上,因此如果您想更新某些控件,可以在此处安全地执行此操作.接下来ResponseCallback():
private void ResponseCallback(object state) {
var response = _request.EndGetResponse(_responseAsyncResult) as HttpWebResponse;
long contentLength = response.ContentLength;
if (contentLength == -1) {
// You'll have to figure this one out.
}
Stream responseStream = response.GetResponseStream();
GetContentWithProgressReporting(responseStream, contentLength);
response.Close();
}
Run Code Online (Sandbox Code Playgroud)
它被强制object通过AsyncCallback委托签名获取参数,但我没有使用它.它要求EndGetResponse()与IAsyncResult我们之前得到的,现在我们可以进行如下,如果我们没有使用asyncronous电话.但由于这是一个异步回调,它可能正在工作线程上执行,所以不要在这里直接更新任何控件.
无论如何,它从响应中获取内容长度,如果您想要计算下载进度,则需要这样做.有时提供此信息的标头不存在,您得到-1.这意味着您可以自行进行进度计算,并且您必须找到其他方法来了解您正在下载的文件的总大小.在我的情况下,将变量设置为某个值就足够了,因为我不关心数据本身.
之后,它获取表示响应的流并将其传递给执行下载和进度报告的辅助方法:
private byte[] GetContentWithProgressReporting(Stream responseStream, long contentLength) {
UpdateProgressBar(0);
// Allocate space for the content
var data = new byte[contentLength];
int currentIndex = 0;
int bytesReceived = 0;
var buffer = new byte[256];
do {
bytesReceived = responseStream.Read(buffer, 0, 256);
Array.Copy(buffer, 0, data, currentIndex, bytesReceived);
currentIndex += bytesReceived;
// Report percentage
double percentage = (double)currentIndex / contentLength;
UpdateProgressBar((int)(percentage * 100));
} while (currentIndex < contentLength);
UpdateProgressBar(100);
return data;
}
Run Code Online (Sandbox Code Playgroud)
此方法仍可能在工作线程上(因为它由回调调用),因此从此处更新控件仍然不安全.
代码在下载文件的示例中很常见.它分配一些内存来存储文件.它分配一个缓冲区以从流中获取文件块.在循环中,它抓取一个块,将块放入更大的数组中,并计算进度百分比.当它下载尽可能多的字节时,它就会退出.这有点麻烦,但是如果你要使用让你一次下载文件的技巧,你将无法在中间报告下载进度.
(有一件事我觉得有必要指出:如果你的块太小,你将很快更新进度条,这仍然会导致表单锁定.我通常会保持Stopwatch运行并尝试不更新进度条每秒超过两次,但还有其他方法来限制更新.)
无论如何,唯一剩下的就是实际更新进度条的代码,并且它有自己的方法:
private void UpdateProgressBar(int percentage) {
// If on a worker thread, marshal the call to the UI thread
if (progressBar1.InvokeRequired) {
progressBar1.Invoke(new Action<int>(UpdateProgressBar), percentage);
} else {
progressBar1.Value = percentage;
}
}
Run Code Online (Sandbox Code Playgroud)
如果InvokeRequired返回true,则自动调用Invoke().如果碰巧在正确的线程上,它会更新进度条.如果你碰巧使用WPF,有类似的方法来编组调用,但我相信它们是通过Dispatcher对象发生的.
我知道这很多.这就是为什么新async东西如此之大的部分原因.该IAsyncResult模式功能强大,但不会从您身上抽象出许多细节.但即使在较新的模式中,您也必须跟踪更新进度条的安全时间.
需要考虑的事项:
GetResponse(),则在调用时会抛出异常EndGetResponse().Stream通过检查其Length财产来询问,但我发现这是不可靠的.如果不确定的话,A Stream可以自由地抛出NotSupportedException或返回一个值-1,如果你事先没有告诉你预期有多少数据,那么你通常可以使用网络流,然后你只能继续问:"还有更多吗?"HttpWebRequest.BeginGetRequestStream()因此您可以异步写入要上载的文件.有关更新控件的所有警告均适用.