-1

我正在试验/学习新的任务库,并使用 WebClient 和 Task.Run 编写了一个非常简单的 html 下载器。但是,我的网络使用率永远不会超过 5%。我想了解为什么以及如何改进我的代码以达到 100% 的网络使用率/吞吐量(可能不可能,但必须远远超过 5%)。

我也希望能够限制线程的数量,但它似乎并不像我想象的那么容易(即自定义任务调度程序)。有没有办法做这样的事情来设置最大线程数:something.SetMaxThread(2)?

internal static class Program
    {
        private static void Main()
        {
            for (var i = 0; i < 1000000; i++)
            {
                Go(i, Thread.CurrentThread.ManagedThreadId);
            }

            Console.Read();
        }

        private static readonly Action<int, int> Go = (counter, threadId) => Task.Run(() =>
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var webClient = new WebClient();
            webClient.DownloadString(new Uri("http://stackoverflow.com"));

            stopwatch.Stop();

            Console.Write("{0} == {1} | ", threadId.ToString("D3"), Thread.CurrentThread.ManagedThreadId.ToString("D3"));
            Console.WriteLine("{0}: {1}ms ", counter.ToString("D3"), stopwatch.ElapsedMilliseconds.ToString("D4"));
        });
    }

这是根据@spender 的异步版本。但是我的理解是 await 将“记住”时间点并将下载移交给操作系统级别并跳过(2 console.write)并立即返回 main 并继续在 for 循环中调度剩余的 Go 方法。我理解正确吗?所以UI上没有阻塞。

private static async void Go(int counter, int threadId)
{
    using (var webClient = new WebClient())
    {
        var stopWatch = new Stopwatch();
        stopWatch.Start();

        await webClient.DownloadStringTaskAsync(new Uri("http://ftp.iinet.net.au/test500MB.dat"));

        stopWatch.Stop();

        Console.Write("{0} == {1} | ", threadId.ToString("D3"), Thread.CurrentThread.ManagedThreadId.ToString("D3"));
        Console.WriteLine("{0}: {1}ms ", counter.ToString("D3"), stopWatch.ElapsedMilliseconds.ToString("D4"));
    }
}

我注意到的是,当我下载大文件时,下载速度/网络使用情况没有太大差异。它们(线程版本和异步版本)都达到了大约 12.5% 的网络使用率和大约 12MByte 下载/秒的峰值。我还尝试运行多个实例(运行多个 .exe),而且两者之间没有太大区别。当我尝试同时从 2 个 URL(20 个实例)下载大文件时,我得到了相似的网络使用率(12.5%)和下载速度(10-12MByte /sec)。我想我达到了顶峰?

4

1 回答 1

5

就目前而言,您的代码不是最佳的,因为尽管您使用 Task.Run 创建在 ThreadPool 中运行的异步代码,但在 ThreadPool 中运行的代码仍然阻塞在线:

webClient.DownloadString(...

这相当于滥用 ThreadPool,因为它不是为运行阻塞任务而设计的,并且启动额外线程以处理工作负载峰值的速度很慢。这反过来将对任何使用 ThreadPool 的 API(计时器、异步回调,它们无处不在)的平稳运行产生严重的降级影响,因为它们会将工作安排到(饱和)队列的后面ThreadPool(它不情愿地缓慢地捆绑着数百个线程,这些线程将花费 99.9% 的时间什么都不做)。

停止阻塞 ThreadPool 并切换到不阻塞的正确异步方法。

因此,现在您可以使用以下简单的 mod 从字面上破坏您的路由器并严重扰乱 SO 站点管理员:

   private static void Main()
    {
        for (var i = 0; i < 1000000; i++)
        {
            Go(i, Thread.CurrentThread.ManagedThreadId);
        }

        Console.Read();
    }

    private static async Task Go(int counter, int threadId)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();

        using (var webClient = new WebClient())
        {
            await webClient.DownloadStringTaskAsync(
                             new Uri("http://stackoverflow.com"));
        }
            //...
    }

HttpWebRequest(以及因此的WebClient)也受到许多限制。

于 2013-06-20T01:37:19.877 回答