使用HttpWebRequest类在上传和下载时显示百分比的进度

Meh*_*ran 5 c# progress httpwebrequest

我试图上传和下载使用(在相同的请求)发送到服务器HttpWebRequestC#,并由于数据的大小相当大(考虑网速),我想展示如何工作的远做的用户又有多少是离开(不是几秒钟,而是百分比).

我已经阅读了几个尝试实现这一点的例子,但没有一个显示任何进度条.它们都只是用来async在上传/下载时不阻止UI.而且他们主要关注上传/下载,没有人尝试将它们都包含在同一个请求中.

由于我使用.Net 4作为我的目标框架,我async自己无法实现一个方法.如果您要建议任何异步,请使用Begin...方法而不是await关键字!谢谢.

Owe*_*enP 34

你需要知道一些事情才能成功.

第0步:保持.NET 4.0的文档方便:

如果你查看HttpWebRequest文档,你会发现它GetResponse()有两个同名的方法:BeginGetResponse()EndGetResponse().这些方法使用最古老的.NET异步模式,称为"IAsyncResult模式".有关此模式的数千页文本,如果您需要详细信息,可以阅读教程.这是速成课程:

  1. 对于方法Foo(),BeginFoo()可以使用参数并且必须返回IAsyncResult实现.此方法执行任务Foo()执行,但在不阻止当前线程的情况下执行工作.
  2. 如果BeginFoo()采用AsyncCallback参数,它希望您提供一个它将在完成后调用的委托.(这是了解它已完成的几种方法之一,但恰好是技术HttpWebRequest用途.)
  3. 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()因此您可以异步写入要上载的文件.有关更新控件的所有警告均适用.