c# - 从 API 下载 JSON 文件并将其保存在文件中
问题描述
我尝试编写一个应用程序,它需要一堆 URL 并将它们的内容异步保存在单独的文件中。我将该代码编写为同步的,它工作得很好,所以我试图让它异步。问题是我遇到了一些异常:该进程无法访问该文件,因为它正在被另一个进程使用。我对流不太了解,但是否有可能 2 个线程共享同一个流并暂时关闭“他们的”文件但不完全关闭,这就是我遇到该错误的原因?如果不是,那会是什么?
public override async Task ExecuteCommandAsync(IEnumerable<string> urls)
{
string directory = "some directory";
int i = 0;
foreach (var url in urls)
{
tasks.Add(Task.Run(async () =>
{
try
{
await DownloadJsonFromUrl(url, directory, i);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}));
i++;
Console.WriteLine($"task nr {i} started.");
}
await Task.WhenAll(tasks);
private async Task DownloadJsonFromUrl(string url, string directory, int fileNumber)
{
using (var httpClient = _clientFactory.CreateClient())
using (var response = await httpClient.GetAsync(url,
HttpCompletionOption.ResponseHeadersRead))
using (FileStream fileStream = File.Open(directory + fileNumber.ToString() + ".json",
FileMode.Create, FileAccess.Write, FileShare.None))
using (var clientStream = await response.Content.ReadAsStreamAsync())
{
await clientStream.CopyToAsync(fileStream);
}
}
解决方案
您的代码存在很多问题。但是,直接的问题是您i
在lambda中使用,这会创建一个闭包。闭包关闭变量,而不是值。因此,它试图同时一次又一次地写入同一个文件。
以您的原始代码为例,稍作修改。
foreach (var url in urls)
{
tasks.Add(Task.Run(async () =>
{
try
{
Console.WriteLine($"task nr {i} started.");
await Task.Delay(100);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}));
i++;
}
输出将是
task nr 10 started.
task nr 10 started.
task nr 10 started.
task nr 10 started.
task nr 10 started.
task nr 10 started.
task nr 10 started.
task nr 10 started.
task nr 10 started.
task nr 10 started.
最简单的解决方案是创建一个副本:
foreach (var url in urls)
{
var newI = i;
...
tasks.Add(Task.Run(async () =>
...
await DownloadJsonFromUrl(url, directory, newI);
...
但是,让我们更进一步,清理您的任务和 lamdas 并确保您拥有唯一的文件名。为简单起见,我们只使用Select
具有index的重载,然后投影可以等待的任务:
给定
private async Task DownloadJsonFromUrl(string url, string path)
{
using var httpClient = _clientFactory.CreateClient();
using var response = await httpClient
.GetAsync(url, HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await using var fileStream = new FileStream(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
1024*80,
FileOptions.Asynchronous);
await using var clientStream = await response.Content
.ReadAsStreamAsync()
.ConfigureAwait(false);
await clientStream
.CopyToAsync(fileStream)
.ConfigureAwait(false);
}
用法
public async Task ExecuteCommandAsync(IEnumerable<string> urls)
{
var directory = "some directory";
async Task DownloadAsync(string url, int i)
{
try
{
Console.WriteLine($"task nr {i} started.");
await DownloadJsonFromUrl(url, Path.Combine(directory, $"{i}.json"));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
var tasks = urls.Select(DownloadAsync);
await Task.WhenAll(tasks);
}
推荐阅读
- python - 为什么“DataFrame”对象不可调用
- python - cmap.set_under() 和 cmap.set_over() 没有出现在颜色栏中
- javascript - Javascript将一个元素添加到另一个没有id的元素中
- c++ - 在 gmock 和 gtest C++ 中使用带有 TYPED_TEST_SUITE_P 的 Mock 类
- python - Python - discord.py on_message - 传递位置参数
- python - numpy.savetxt 中的哪个 fmt 选项保持无限整数精度?
- perl - Find::File::Rule 排除子目录
- javascript - 范围 0.01-100000 JS 的正则表达式
- reinforcement-learning - 在多智能体环境中降低一个智能体的动作采样频率
- laravel - 即使在 .ebextensions 配置文件中设置了权限,Laravel 在 ElasticBeanstalk 上的“日志文件权限被拒绝”错误也会不断出现