RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 643978
Accepted
Monk
Monk
Asked:2020-03-25 18:09:26 +0000 UTC2020-03-25 18:09:26 +0000 UTC 2020-03-25 18:09:26 +0000 UTC

如何确定多线程程序中的下载速度?

  • 772

有一个简单的下载代码:

internal static async Task<ImageFile> DownloadFile(Uri uri)
{
  byte[] result;
  WebResponse response;
  var file = new ImageFile();
  var request = WebRequest.Create(uri);

  try
  {
    response = await request.GetResponseAsync();
    using (var ms = new MemoryStream())
    {
      response.GetResponseStream().CopyTo(ms);
      result = ms.ToArray();
    }
  }
  catch (System.Exception ex) { }
  if (response.ContentLength == result.LongLength)
    file.Body = result;
  return file;
}

我想添加一个下载速度指示器。谷歌建议你可以关注阅读流的速度:

result = await CopyTo(response.GetResponseStream(), response.ContentLength, progressChanged);

private static async Task<byte[]> CopyTo(Stream from, long totalBytes, Action<DownloadProgress> loadedEvent)
{
  var sw = new Stopwatch();
  sw.Start();
  var data = new byte[totalBytes];
  byte[] buffer = new byte[81920];
  int currentIndex = 0;
  while (true)
  {
    int num = await from.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
    if (num != 0)
    {
      Array.Copy(buffer, 0, data, currentIndex, num);
      currentIndex += num;
      loadedEvent?.Invoke(new DownloadProgress(currentIndex, totalBytes, sw.ElapsedMilliseconds));
    }
    else
      break;
  }
  sw.Stop();
  return data;
}

为了存储信息(并向上滚动),我开始了一个简单的结构:

public struct DownloadProgress
{
  public readonly long BytesReceived;

  public readonly long TotalBytesToReceive;

  public readonly long TimeMs;

  public double GetSpeed()
  {
    var seconds = TimeMs / 1000.0;
    if (seconds > 0)
      return BytesReceived / seconds;
    return 0;
  }

  public DownloadProgress(long received, long total, long time)
  {
    this.BytesReceived = received;
    this.TotalBytesToReceive = total;
    this.TimeMs = time;
  }
}

总的来说,如果你在一个流中下载,那么GetSpeed在任何时候(除了某处的第一秒)它都会显示真实速度。

下载总图的类最终存储:

    this.Speed = 0;
    var file = await ImageFile.DownloadFile(this.ImageLink, dp => this.Speed = dp.GetSpeed());
    this.Speed = 0;

然后我认为剩下的是最简单的 - 我只是在顶层添加了所有速度,仅此而已:

return this.ActivePages != null && this.ActivePages.Any() ? 
  this.ActivePages.Sum(p => p.Speed) : 0;

事实上,结果是非常不愉快的行为:

  1. 大多数时候速度实际上是正确显示的。

  2. 速度经常跳跃,点差有时超过通道宽度。对于 650 kb/s 的信道,数字从 300 kb/s 跃升至 5-8 Mb/s。

如果在一次下载结束和下一次下载开始时速度很可能较低,那么从技术上讲,超过通道宽度显然是不可能的。也许我错过了某处的四舍五入,或者Stopwatch对于这样的计算来说不够准确?

UPD:第一个怀疑是有道理的——最初的速度跳跃破坏了整体统计数据。如果速度计算方法是这样进行的:

  public double GetSpeed()
  {
    var seconds = TimeMs / 1000.0;
    if (seconds > 0.1)
      return BytesReceived / seconds;
    return 0;
  }

那个速度一般就够用了,偶尔也会有超过通道宽度的跳跃,但不会超过5%。你可以增加被忽略的时间,这样就完全没有跳跃了。

总分有问题。如果对于一个流,该数字是可靠的,那么对于多线程下载,该数字通常是谎言,指标的平均值结果低于真实值(总时间为总容量)。

UPD2:最小化所有计算,删除结构,将逻辑移至静态类:

  public class NetworkSpeed
  {
    public static double TotalSpeed { get { return totalSpeed; } }

    private static double totalSpeed = 0;

    private const uint Seconds = 3;

    private const uint TimerInterval = 1000;

    private static Timer speedTimer = new Timer(state =>
    {
      var now = 0L;
      while (receivedStorage.Value.Any())
      {
        long added;
        if (receivedStorage.Value.TryDequeue(out added))
        {
          now += added;
        }
      }
      lastSpeeds.Value.Enqueue(now);
      totalSpeed = lastSpeeds.Value.Average();
    }, null, 0, TimerInterval);

    private static Lazy<LimitedConcurrentQueue<double>> lastSpeeds = new Lazy<LimitedConcurrentQueue<double>>(() => new LimitedConcurrentQueue<double>(Seconds));

    private static Lazy<ConcurrentQueue<long>> receivedStorage = new Lazy<ConcurrentQueue<long>>();

    public static void Clear()
    {
      while (receivedStorage.Value.Count > 0)
      {
        long dd;
        receivedStorage.Value.TryDequeue(out dd);
      }
      while (lastSpeeds.Value.Count > 0)
      {
        double dd;
        lastSpeeds.Value.TryDequeue(out dd);
      }
    }

    public static void AddInfo(long received)
    {
      receivedStorage.Value.Enqueue(received);
    }

    private class LimitedConcurrentQueue<T> : ConcurrentQueue<T>
    {
      public uint Limit { get; }

      public new void Enqueue(T item)
      {
        while (Count >= Limit)
        {
          T deleted;
          TryDequeue(out deleted);
        }
        base.Enqueue(item);
      }

      public LimitedConcurrentQueue(uint limit)
      {
        Limit = limit;
      }
    }
  }

结果,在下载时,报告下一时刻下载了多少字节就足够了:

      NetworkSpeed.AddInfo(num);

就是这样,指示器NetworkSpeed.TotalSpeed将显示过去 3 秒内的平均速度。平均指标总体上或多或少变得稳定,尽管它略微高估了我数据中的指标。好吧,很明显,如果线程池过载,那么计时器将无法及时计时,速度将开始“跳跃”。

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Monk
    2020-03-27T00:01:11Z2020-03-27T00:01:11Z

    我正在发布对我有用的解决方案。最终数字的准确率为95-99%。

      public class NetworkSpeed
      {
        public static double TotalSpeed { get { return totalSpeed; } }
    
        private static double totalSpeed = 0;
    
        private const uint Seconds = 3;
    
        private const uint TimerInterval = 1000;
    
        private static Timer speedTimer = new Timer(state =>
        {
          var now = 0L;
          while (receivedStorage.Value.Any())
          {
            long added;
            if (receivedStorage.Value.TryDequeue(out added))
            {
              now += added;
            }
          }
          lastSpeeds.Value.Enqueue(now);
          totalSpeed = lastSpeeds.Value.Average();
        }, null, 0, TimerInterval);
    
        private static Lazy<LimitedConcurrentQueue<double>> lastSpeeds = new Lazy<LimitedConcurrentQueue<double>>(() => new LimitedConcurrentQueue<double>(Seconds));
    
        private static Lazy<ConcurrentQueue<long>> receivedStorage = new Lazy<ConcurrentQueue<long>>();
    
        public static void Clear()
        {
          while (receivedStorage.Value.Count > 0)
          {
            long dd;
            receivedStorage.Value.TryDequeue(out dd);
          }
          while (lastSpeeds.Value.Count > 0)
          {
            double dd;
            lastSpeeds.Value.TryDequeue(out dd);
          }
        }
    
        public static void AddInfo(long received)
        {
          receivedStorage.Value.Enqueue(received);
        }
    
        private class LimitedConcurrentQueue<T> : ConcurrentQueue<T>
        {
          public uint Limit { get; }
    
          public new void Enqueue(T item)
          {
            while (Count >= Limit)
            {
              T deleted;
              TryDequeue(out deleted);
            }
            base.Enqueue(item);
          }
    
          public LimitedConcurrentQueue(uint limit)
          {
            Limit = limit;
          }
        }
      }
    

    如何使用它 - 在顶层,我们Clear()在开始或完成下载时调用它,以便结果独立于其他下载。在实际下载发生的地方,我们调用方法AddInfo,指示在下一个下载周期我们收到了多少字节。可以CopyTo从标题或DownloadProgressChangedy使用WebClient。最主要的是准确传达先前指标与当前指标之间的差异。

    测量的准确性由计时器 ( System.Threading.Timer) 提供,因此,为了使读数的准确性可靠,线程池必须可以自由调用callback。

    嗯,当然,所有测量的结果都在TotalSpeed. 如果需要,您可以添加有关其更改的事件,以便及时显示在 UI 中。它的更新频率特别与定时器同步——否则数字变化太频繁,用户不明白速度是多少。

    • 4

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5