【问题标题】:Run Async every x number of times in a for loop在 for 循环中每 x 次运行 Async
【发布时间】:2017-08-04 21:39:54
【问题描述】:

我正在下载 100K+ 个文件,并希望以补丁的形式进行,例如一次 100 个文件。

static void Main(string[] args) {
    Task.WaitAll(
      new Task[]{
           RunAsync()
    });
}

// each group has 100 attachments.
static async Task RunAsync() {
    foreach (var group in groups) {
        var tasks = new List<Task>();
        foreach (var attachment in group.attachments) {
            tasks.Add(DownloadFileAsync(attachment, downloadPath));
        }
        await Task.WhenAll(tasks);
    }
}

static async Task DownloadFileAsync(Attachment attachment, string path) {
    using (var client = new HttpClient()) {
        using (var fileStream = File.Create(path + attachment.FileName)) {
            var downloadedFileStream = await client.GetStreamAsync(attachment.url);
            await downloadedFileStream.CopyToAsync(fileStream);
        }
    }
}

预期 希望它一次下载100个文件,然后再下载下100个;

实际 它同时下载更多。赶紧报错Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host

【问题讨论】:

  • 很遗憾它被标记为重复,因为另一个问题使用了截然不同的方法,我很高兴知道为什么 Quentin 使用的方法失败了。
  • 我同意;不是重复的。我的猜测是 HttpClient 方法比您希望的更早返回。
  • 我在 .NET Core 中使用 Web 服务时遇到了类似的问题。将任务放入队列中,一旦任务完成,从队列中取出并运行任务。你当然应该同步队列。那应该可以。
  • 谢谢@MertAkcakaya 将尝试队列方法。

标签: c# async-await


【解决方案1】:

就性能而言,“批量”运行任务并不是一个好主意。长时间运行的任务会使整个批处理阻塞。更好的方法是在完成一项新任务后立即开始。

这可以通过@MertAkcakaya 建议的队列来实现。但我会根据我的另一个答案Have a set of Tasks with only X running at a time

发布另一个替代方案
int maxTread = 3;
System.Net.ServicePointManager.DefaultConnectionLimit = 50; //Set this once to a max value in your app

var urls = new Tuple<string, string>[] {
    Tuple.Create("http://cnn.com","temp/cnn1.htm"),
    Tuple.Create("http://cnn.com","temp/cnn2.htm"),
    Tuple.Create("http://bbc.com","temp/bbc1.htm"),
    Tuple.Create("http://bbc.com","temp/bbc2.htm"),
    Tuple.Create("http://stackoverflow.com","temp/stackoverflow.htm"),
    Tuple.Create("http://google.com","temp/google1.htm"),
    Tuple.Create("http://google.com","temp/google2.htm"),
};
DownloadParallel(urls, maxTread);

async Task DownloadParallel(IEnumerable<Tuple<string,string>> urls, int maxThreads)
{
    SemaphoreSlim maxThread = new SemaphoreSlim(maxThreads);
    var client = new HttpClient();

    foreach(var url in urls)
    {
        await maxThread.WaitAsync();
        DownloadFile(client, url.Item1, url.Item2)
                    .ContinueWith((task) => maxThread.Release() );
    }
}


async Task DownloadFile(HttpClient client, string url, string fileName)
{
    var stream = await client.GetStreamAsync(url);
    using (var fileStream = File.Create(fileName))
    {
        await stream.CopyToAsync(fileStream);
    }
}

PS:DownloadParallel 将在开始最后一次下载后立即返回。所以不要等待它。如果你真的想等待它,你应该在方法的末尾添加for (int i = 0; i &lt; maxThreads; i++) await maxThread.WaitAsync();

PS2:不要忘记将异常处理添加到 DownloadFile

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-21
    • 2023-01-04
    相关资源
    最近更新 更多