我正在使用C#和WebClient类。
这是我正在使用的代码,受到SO上另一篇文章的启发。此代码适用于大型文件,它可以准确显示下载速度。但是,现在有下载单个文件的限制,其中许多文件很小,小到0.5-5 MB。这导致速度反击猛涨,通常达到数十万KBps。我不知道还有什么可以打击这个。我添加了第二个进度条,显示单个文件下载,这有助于改善图像,但下载速度计数器应该是固定的。是否有一个不同的类可以解决这个问题?
此代码中的WebClient在其他地方正确处理。 私人类NetSpeedCounter { private double [] DataPoints;
private DateTime LastUpdate;
private int NumCounts = 0;
private int PrevBytes = 0;
public double Speed { get; private set; }
public NetSpeedCounter(WebClient webClient, int maxPoints = 10)
{
DataPoints = new double[maxPoints];
Array.Clear(DataPoints, 0, DataPoints.Length);
webClient.DownloadProgressChanged += (sender, e) =>
{
var msElapsed = DateTime.Now - LastUpdate;
int curBytes = (int)(e.BytesReceived - PrevBytes);
PrevBytes = (int)e.BytesReceived;
double dataPoint = ((double)curBytes) / msElapsed.TotalSeconds;
DataPoints[NumCounts++ % maxPoints] = dataPoint;
Speed = DataPoints.Average();
};
}
public void Reset()
{
PrevBytes = 0;
LastUpdate = DateTime.Now;
}
}
我使用此代码下载文件,然后通过调用DownloadFileAsync启动该代码。这段代码只是异步地将它们一个接一个地下载到一个链中。
这是设置开始下载 队列记录Q =新队列(文件);
progressBar.Value = 0;
progressBar.Maximum = recordQ.Count;
UpdateStatusText("Downloading " + recordQ.Count + " files");
var record = recordQ.Dequeue();
speedUpdater.Start();
CheckAndCreate(record.AbsolutePath);
添加事件处理程序
wc.DownloadFileCompleted += (sender, e) =>
{
var nr = recordQ.Dequeue();
CheckAndCreate(nr.AbsolutePath);
this.Invoke((MethodInvoker)delegate
{
UpdateStatusText("Downloading " + recordQ.Count + " files", lblStatusR.Text);
});
counter.Reset();
// download the next one
wc.DownloadFileAsync(nr.DownloadPath, nr.AbsolutePath);
}
counter.Start();
wc.DownloadFileAsync(record.DownloadPath, record.AbsolutePath);
这最后一个电话是关闭所有事情的。
答案 0 :(得分:2)
DateTime.Now
不够准确(Eric Lippert提到它们的精度为30 ms here),因为DateTime.Now将返回先前使用的DateTime。现在连续快速召唤。这可能会导致速度计数器出现差异,因为下载快速完成后会出现不准确的增加。我建议您使用StopWatch
API。
修改强>
我已根据您的代码创建了以下测试Winforms应用程序,该代码适用于小文件。我在内联网上获得了一个合理的200 kbps的5个文件,每个文件大约2MB。只要确保你在正确的地方叫秒表课程。 要复制,请创建一个winforms应用程序,创建Id lblSpeed,lblStatus,lblFile 的3个标签,然后复制\粘贴代码并将下面的URI重命名为要测试的文件。
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Queue<Record> recordQ;
WebClient wc;
NetSpeedCounter counter;
//We store downloaded files in C:\TestDir (hardcoded in the Record class below)
public Form1()
{
InitializeComponent();
recordQ = new Queue<Record>();
//replace the URI string below. Nothing else to replace.
//recordQ.Enqueue(new Record(@"URI1", "SQLtraining.exe"));
//recordQ.Enqueue(new Record(@"URI2", "Project Mgmt.pptx"));
//first uri to process. Second param is the file name that we store.
Record record = new Record(@"URI0","Agile.pptx"); // replace the URI
//Initialize a webclient and download the first record
using (wc = new WebClient())
{
counter = new NetSpeedCounter(wc);
wc.DownloadFileCompleted += (sender, e) =>
{
if (recordQ.Count == 0)
{
UpdateStatusText("Done");
return;
}
var nr = recordQ.Dequeue();
//just create directory. the code uses the same directory
CheckAndCreate(nr.Directory);
//need not even use invoke here. Just a plain method call will suffice.
this.Invoke((MethodInvoker)delegate
{
UpdateStatusText("Left to process: " + recordQ.Count + " files");
});
counter.Reset();
counter.Start();
//continue with rest of records
wc.DownloadFileAsync(nr.DownloadPath, nr.GetFullPath());
this.lblFile.Text = nr.DownloadPath.OriginalString;
};
//just update speed in UI
wc.DownloadProgressChanged += wc_DownloadProgressChanged;
counter.Start();
//display URI we are downloading
this.lblFile.Text = record.DownloadPath.OriginalString;
//start first download
wc.DownloadFileAsync(record.DownloadPath, record.GetFullPath());
}
}
void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.lblSpeed.Text = counter.Speed.ToString();
}
public void UpdateStatusText(string msg)
{
this.lblStatus.Text = msg;
}
public void CheckAndCreate(string absPath)
{
if (!Directory.Exists(absPath))
Directory.CreateDirectory(absPath);
}
}
public class NetSpeedCounter
{
private int NumCounts = 0;
private int PrevBytes = 0;
private Stopwatch stopwatch;
public double Speed { get; private set; }
double[] DataPoints;
public NetSpeedCounter(WebClient webClient, int maxPoints = 10)
{
DataPoints = new double[maxPoints];
stopwatch = new Stopwatch();
Array.Clear(DataPoints, 0, DataPoints.Length);
webClient.DownloadProgressChanged += (sender, e) =>
{
var msElapsed = DateTime.Now - LastUpdate;
stopwatch.Stop();
int curBytes = (int)(e.BytesReceived - PrevBytes);
PrevBytes = (int)e.BytesReceived;
//record in kbps
double dataPoint = (double)curBytes / (stopwatch.ElapsedMilliseconds);
DataPoints[NumCounts++ % maxPoints] = dataPoint;
//protect NumCount from overflow
if (NumCounts == Int32.MaxValue)
NumCounts = 0;
Speed = DataPoints.Average();
stopwatch.Start();
};
}
public void Start()
{
stopwatch.Start();
}
public void Reset()
{
PrevBytes = 0;
stopwatch.Reset();
}
}
public class Record
{
public string Directory;
public string File;
public Uri DownloadPath;
public Record(string uriPath, string fileOutputName)
{
this.Directory = @"C:\TestDir\";
this.DownloadPath = new Uri(uriPath);
this.File = fileOutputName;
}
public string GetFullPath()
{
return this.Directory + this.File;
}
}
}
答案 1 :(得分:0)
我想出了一种不同的做法。
我现在递增一个具有字节数的计数器,而不是为每个ProgressChanged事件添加数据点。此计数器保留在多个文件中,然后我只使用计时器获取数据点,并使用System.Diagnostic.Stopwatch
获取时间。效果非常好,准确性要好得多,我强烈建议这样做。
这是一些代码
class NetSpeedCounter
{
private Stopwatch watch;
private long NumCounts = 0;
private int PrevBytes = 0;
private double[] DataPoints;
private long CurrentBytesReceived = 0;
public double Speed { get; private set; }
private System.Timers.Timer ticker = new System.Timers.Timer(100);
public NetSpeedCounter(WebClient webClient, int maxPoints = 5)
{
watch = new System.Diagnostics.Stopwatch();
DataPoints = new double[maxPoints];
webClient.DownloadProgressChanged += (sender, e) =>
{
int curBytes = (int)(e.BytesReceived - PrevBytes);
if (curBytes < 0)
curBytes = (int)e.BytesReceived;
CurrentBytesReceived += curBytes;
PrevBytes = (int)e.BytesReceived;
};
ticker.Elapsed += (sender, e) =>
{
double dataPoint = (double)CurrentBytesReceived / watch.ElapsedMilliseconds;
DataPoints[NumCounts++ % maxPoints] = dataPoint;
Speed = DataPoints.Average();
CurrentBytesReceived = 0;
watch.Restart();
};
}
public void Stop()
{
watch.Stop();
ticker.Stop();
}
public void Start()
{
watch.Start();
ticker.Start();
}
public void Reset()
{
CurrentBytesReceived = 0;
PrevBytes = 0;
watch.Restart();
ticker.Start();
}
}