问题描述
在大家对这是一个重复的问题进行大肆宣传之前,我花了两天的时间解决这个问题,观看了有关异步编程的youtube教程,浏览了类似的stackoverflow帖子等,但我无法终生了解如何将文件异步并行下载应用到我的项目中。
首先,一些背景:
我正在创建一个程序,当通过用户进行查询输入时,它将调用twitch API并下载剪辑。
我的程序分为两个部分
1-一种Web抓取工具,它会生成一个.json文件,其中包含下载文件所需的所有详细信息和
2-一个下载器。
“我的下载器”包含对Data
类的引用,该类是常见属性和方法的处理程序,例如我的ClientID
,Authentication
,OutputPath
,JsonFile
, QueryURL
。它还包含为这些属性赋值的方法。
这是问题所在的FileDownloader.cs的两种方法:
public async static void DownloadAllFiles(Data clientData)
{
data = clientData;
data.OutputFolderExists();
// Deserialize .json file and get ClipInfo list
List<ClipInfo> clips = JsonConvert.DeserializeObject<List<ClipInfo>>(File.ReadAllText(data.JsonFile));
tasks = new List<Task>();
foreach(ClipInfo clip in clips)
{
tasks.Add(DownloadFilesAsync(clip));
}
await Task.WhenAll(tasks);
}
private async static Task DownloadFilesAsync(ClipInfo clip)
{
WebClient client = new WebClient();
string url = GetClipURL(clip);
string filepath = data.OutputPath + clip.id + ".mp4";
await client.DownloadFileTaskAsync(new Uri(url),filepath);
}
这只是我尝试下载文件的许多尝试之一,我从这篇博文中得到了一个想法:
我还尝试了IAmTimCorey的YouTube视频中的以下方法:
我花了一个小时来解决这个问题,老实说,我不知道为什么我的任何尝试都不起作用。非常感谢您的帮助。
谢谢
本
下面是我的完整代码,如果有人出于任何原因需要它。
代码结构:
我下载的唯一外部库是Newtonsoft.Json
ClipInfo.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Downloader
{
public class ClipInfo
{
public string id { get; set; }
public string url { get; set; }
public string embed_url { get; set; }
public string broadcaster_id { get; set; }
public string broadcaster_name { get; set; }
public string creator_id { get; set; }
public string creator_name { get; set; }
public string video_id { get; set; }
public string game_id { get; set; }
public string language { get; set; }
public string title { get; set; }
public int view_count { get; set; }
public DateTime created_at { get; set; }
public string thumbnail_url { get; set; }
}
}
Pagination.cs
namespace Downloader
{
public class Pagination
{
public string cursor { get; set; }
}
}
Root.cs
using System.Collections.Generic;
namespace Downloader
{
public class Root
{
public List<ClipInfo> data { get; set; }
public Pagination pagination { get; set; }
}
}
Data.cs
using System;
using System.IO;
namespace Downloader
{
public class Data
{
private static string directory = Directory.GetCurrentDirectory();
private readonly static string defaultJsonFile = directory + @"\clips.json";
private readonly static string defaultOutputPath = directory + @"\Clips\";
private readonly static string clipsLink = "https://api.twitch.tv/helix/clips?";
public string OutputPath { get; set; }
public string JsonFile { get; set; }
public string ClientID { get; private set; }
public string Authentication { get; private set; }
public string QueryURL { get; private set; }
public Data()
{
OutputPath = defaultOutputPath;
JsonFile = defaultJsonFile;
}
public Data(string clientID,string authentication)
{
ClientID = clientID;
Authentication = authentication;
OutputPath = defaultOutputPath;
JsonFile = defaultJsonFile;
}
public Data(string clientID,string authentication,string outputPath)
{
ClientID = clientID;
Authentication = authentication;
OutputPath = directory + @"\" + outputPath + @"\";
JsonFile = OutputPath + outputPath + ".json";
}
public void GetQuery()
{
Console.Write("Please enter your query: ");
QueryURL = clipsLink + Console.ReadLine();
}
public void GetClientID()
{
Console.WriteLine("Enter your client ID");
ClientID = Console.ReadLine();
}
public void GetAuthentication()
{
Console.WriteLine("Enter your Authentication");
Authentication = Console.ReadLine();
}
public void OutputFolderExists()
{
if (!Directory.Exists(OutputPath))
{
Directory.CreateDirectory(OutputPath);
}
}
}
}
JsonGenerator.cs
using System;
using System.IO;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Linq;
namespace Downloader
{
public static class JsonGenerator
{
// This class has no constructor.
// You call the Generate methods,passing in all required data.
// The file will then be generated.
private static Data data;
public static async Task Generate(Data clientData)
{
data = clientData;
string responseContent = null;
// Loop that runs until the api request goes through
bool authError = true;
while (authError)
{
authError = false;
try
{
responseContent = await GetHttpResponse();
}
catch (HttpRequestException)
{
Console.WriteLine("Invalid authentication,please enter client-ID and authentication again!");
data.GetClientID();
data.GetAuthentication();
authError = true;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
authError = true;
}
}
data.OutputFolderExists();
GenerateJson(responseContent);
}
// Returns the contents of the resopnse to the api call as a string
private static async Task<string> GetHttpResponse()
{
// Creating client
HttpClient client = new HttpClient();
if (data.QueryURL == null)
{
data.GetQuery();
}
// Setting up request
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get,data.QueryURL);
// Adding Headers to request
requestMessage.Headers.Add("client-id",data.ClientID);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer",data.Authentication);
// Receiving response to the request
HttpResponseMessage responseMessage = await client.SendAsync(requestMessage);
// Gets the content of the response as a string
string responseContent = await responseMessage.Content.ReadAsstringAsync();
return responseContent;
}
// Generates or adds to the .json file that contains data on each clip
private static void GenerateJson(string responseContent)
{
// Parses the data from the response to the api request
Root responseResult = JsonConvert.DeserializeObject<Root>(responseContent);
// If the file doesn't exist,we need to create it and add a '[' at the start
if (!File.Exists(data.JsonFile))
{
FileStream file = File.Create(data.JsonFile);
file.Close();
// The array of json objects needs to be wrapped inside []
File.AppendAllText(data.JsonFile,"[\n");
}
else
{
// For a pre-existing .json file,The last object won't have a comma at the
// end of it so we need to add it Now,before we add more objects
string[] jsonLines = File.ReadAllLines(data.JsonFile);
File.WriteallLines(data.JsonFile,jsonLines.Take(jsonLines.Length - 1).ToArray());
File.AppendAllText(data.JsonFile,",");
}
// If the file already exists,but there was no [ at the start for whatever reason,// we need to add it
if (File.ReadAllText(data.JsonFile).Length == 0 || File.ReadAllText(data.JsonFile)[0] != '[')
{
File.WriteallText(data.JsonFile,"[\n" + File.ReadAllText(data.JsonFile));
}
string json;
// Loops through each ClipInfo object that the api returned
for (int i = 0; i < responseResult.data.Count; i++)
{
// Serializes the ClipInfo object into a json style string
json = JsonConvert.SerializeObject(responseResult.data[i]);
// Adds the serialized contents of ClipInfo to the .json file
File.AppendAllText(data.JsonFile,json);
if (i != responseResult.data.Count - 1)
{
// All objects except the last require a comma at the end of the
// object in order to correctly format the array of json objects
File.AppendAllText(data.JsonFile,");
}
// Adds new line after object entry
File.AppendAllText(data.JsonFile,"\n");
}
// Adds the ] at the end of the file to close off the json objects array
File.AppendAllText(data.JsonFile,"]");
}
}
}
FileDownloader.cs
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace Downloader
{
public class FileDownloader
{
private static Data data;
private static List<Task> tasks;
public async static void DownloadAllFiles(Data clientData)
{
data = clientData;
data.OutputFolderExists();
// Deserialize .json file and get ClipInfo list
List<ClipInfo> clips = JsonConvert.DeserializeObject<List<ClipInfo>>(File.ReadAllText(data.JsonFile));
tasks = new List<Task>();
foreach (ClipInfo clip in clips)
{
tasks.Add(DownloadFilesAsync(clip));
}
await Task.WhenAll(tasks);
}
private static void GetData()
{
if (data.ClientID == null)
{
data.GetClientID();
}
if (data.Authentication == null)
{
data.GetAuthentication();
}
if (data.QueryURL == null)
{
data.GetQuery();
}
}
private static string GetClipURL(ClipInfo clip)
{
// Example thumbnail URL:
// https://clips-media-assets2.twitch.tv/AT-cm%7C902106752-preview-480x272.jpg
// You can get the URL of the location of clip.mp4
// by removing the -preview.... from the thumbnail url */
string url = clip.thumbnail_url;
url = url.Substring(0,url.IndexOf("-preview")) + ".mp4";
return url;
}
private async static Task DownloadFilesAsync(ClipInfo clip)
{
WebClient client = new WebClient();
string url = GetClipURL(clip);
string filepath = data.OutputPath + clip.id + ".mp4";
await client.DownloadFileTaskAsync(new Uri(url),filepath);
}
private static void FileDownloadComplete(object sender,System.ComponentModel.AsyncCompletedEventArgs e)
{
tasks.Remove((Task)sender);
}
}
}
Program.cs
using System;
using System.Threading.Tasks;
using Downloader;
namespace ClipDownloader
{
class Program
{
private static string clientID = "{your_client_id}";
private static string authentication = "{your_authentication}";
async static Task Main(string[] args)
{
Console.WriteLine("Enter your output path");
string outputPath = Console.ReadLine();
Data data = new Data(clientID,authentication,outputPath);
Console.WriteLine(data.OutputPath);
//await JsonGenerator.Generate(data);
FileDownloader.DownloadAllFiles(data);
}
}
}
我通常输入的示例查询是“ game_id = 510218”
解决方法
async void
是您的问题
更改
public static async void DownloadAllFiles(Data clientData)
收件人
public static async Task DownloadAllFiles(Data clientData)
然后您可以等待
await FileDownloader.DownloadAllFiles(data);
更长的故事:
异步void 运行时未观察到(触发并忘记)。您不能等他们完成。本质上,程序一旦开始执行任务,它就会完成,并拆除 App Domain 和所有子任务,使您相信没有任何作用。
,我正在尽力将话题保持在最佳状态,但是在使用JsonConvert.DeserializeObject {T}时,T是否不应该是封装根对象类型?我从来没有像您使用它那样使用过它,所以我很好奇这是否可能是您的错误。我可能完全错了,如果我愿意的话,请饶我一臂之力,但是JSON是key:基于值的。直接反序列化到列表实际上没有任何意义。除非反序列化器中有特殊情况? List将是一个纯粹是ClipInfo值数组的文件,该数组将反序列化为List {T}(私有T [] _items,私有int _size等)的成员。它需要一个父根对象。
int a,b,c,temp,x=123;
a = x/100;
b = (x/10)%10;
c = x%10;
if(b>a){
temp=a;
a=b;
b=temp;
}
if(c>b){
temp=b;
b=c;
c=temp;
}
if(b>a){
temp=a;
a=b;
b=temp;
}
cout << "smallest: " << a+(b*10)+(c*100) << "\n";
cout << "biggest: " << (a*100)+(b*10)+c << "\n";