异步函数未被调用
Async Function Not Getting Called
请原谅我在下面看到的任何笨拙的错误,我正在学习我正在尝试使用的一些概念。
问题:
在调试我的应用程序时,我能够使用 Task.Start()
调用 async
函数。我觉得该应用程序在我所处的阶段处于工作状态,因此使用 CTRL + SHIFT + F9 删除了所有断点。
一旦我 运行 没有断点的应用程序就会失败,因为 属性 没有被填充。现在,当我尝试调试我在 async
函数中设置的任何断点时,处理大部分工作的时间更长。就像它被跳过了一样。谁能看出 GetWowAuctionFileInfo
没有被调用的原因?
GetWowAuctionFileInfo
没有被调用,或者至少看起来没有被调用。
谢谢。
相关代码
调用函数
private void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
调用函数
public void StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
Task t = new Task(() => GetWowAuctionFileInfo(optionalUri));
t.Start();
//Func<string> function = new Func<string>(() => GetWowAuctionFileInfo(optionalUri));
//Task<string> tInfo = Task<string>.Factory.StartNew(() => GetWowAuctionFileInfo(optionalUri));
}
}
}
private async void GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
returnValue = ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
ReturnedData = returnValue;
}
private List<string> ConvertFileInfoToConsumableList(RealmJSFileCheck jsfc)
{
List<string> returnData = new List<string>();
if (jsfc.files.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("File URL: ");
sb.Append(jsfc.files[0].url);
returnData.Add(sb.ToString());
sb = new StringBuilder();
sb.AppendLine("Last Modified: ");
sb.Append(jsfc.files[0].lastModified);
returnData.Add(sb.ToString());
}
else
{
returnData.Add("No File Info Found");
}
return returnData;
}
更新
再次感谢大家的详细评论。我浏览了很多关于 Task 用法的文档,并在这个练习中学到了很多东西。我将@Johnathon 的答案标记为解决方案,因为它提供了我所要求的内容,并提供了非常有用的 link 以获取更多信息。
因为你不等结果。
在分配数据之前,您使用 ReturnedData
循环。
我认为您根本不需要创建新的 Task
。使 GetWowAuctionFileInfo
方法正确异步 returns Task
。
private async Task GetWowAuctionFileInfo(string auctionInfoUri)
{
// same code
}
将 StartTask
更改为 return Task
。因为我们在这里不等待结果,所以我们不需要异步 make 方法。
建议将此方法的名称更改为 LoadData
例如,这将提供有关此方法功能的更多信息。
public Task StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
return GetWowAuctionFileInfo(optionalUri) // this will return Task
}
}
// if validation fails - return completed task or throw exception
return Task.CompletedTask;
}
然后就可以在Button_Click
事件处理器中调用了
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
await w.StartTask("FileInfo", "yourUrl");
// This line will be executed only after asynchronous methods completes succesfully
// or exception will be thrown
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
您的 GetWowAuctionFileInfo
方法是一个异步方法,您等待其中的异步调用而没有 return 任务。一般来说,使用 async void
是不好的做法。相反,将您的 GetWowAuctionFileInfo
方法变成 async Task<List<string>> GetWowAuctionFileInfo
。这将使您等待 GetAsync
调用、解析数据,并将 return 集合发送给调用者,而无需使用 ReturnObject
.
private async Task<List<string>> GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
// You can just return the List<T> now.
return ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
}
因为该方法最初是 async void
,您无法在 buttonTestJSFCHI_Click
中等待调用它。现在我们已经使它全部基于任务,您可以在事件处理程序中等待它。请注意,事件处理程序通常是唯一可以接受的地方 async void
。任何时候您负责方法的创建,并且不受合同约束(如事件处理程序),您应该始终 return 异步方法上的任务。
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
List<string> results = await w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in results)
{
textBoxResults.Text += res;
}
}
public async Task<List<string>> StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
// Since the GetWowAuctionFileInfo now returns Task, we don't need to create a new one. Just await the Task given back to us, and return the given result.
return await GetWowAuctionFileInfo(optionalUri);
}
}
}
您在调试时看到预期结果的原因是调试会话足够慢,以至于异步操作及时完成,您的代码可以使用它。当 运行 应用程序在调试器之外时,它的运行速度快于异步操作可以完成的速度,从而阻止您查看数据。因此需要 await
整个异步调用堆栈,这样您就可以防止在该代码路径下进一步执行,直到您收到所有需要的数据。
Microsoft has a good write up 关于基于任务的编程,我会通读它以帮助您理解它。
编辑
澄清一下,当您在方法上 return 一个 Task<T>
时,您将在 await 时得到结果。例如:
List<string> result = await StartTask();
即使 StartTask
returns Task<List<string>>
,await
操作将等待 StartTask()
方法完成,然后解包来自Task<T>
对象并自动返回结果。所以不要让方法签名欺骗你,如果你 await
它,你将返回结果数据,而不是实际的 Task
本身。您无需手动从 Task
中提取数据。
请原谅我在下面看到的任何笨拙的错误,我正在学习我正在尝试使用的一些概念。
问题:
在调试我的应用程序时,我能够使用 Task.Start()
调用 async
函数。我觉得该应用程序在我所处的阶段处于工作状态,因此使用 CTRL + SHIFT + F9 删除了所有断点。
一旦我 运行 没有断点的应用程序就会失败,因为 属性 没有被填充。现在,当我尝试调试我在 async
函数中设置的任何断点时,处理大部分工作的时间更长。就像它被跳过了一样。谁能看出 GetWowAuctionFileInfo
没有被调用的原因?
GetWowAuctionFileInfo
没有被调用,或者至少看起来没有被调用。
谢谢。
相关代码
调用函数
private void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
调用函数
public void StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
Task t = new Task(() => GetWowAuctionFileInfo(optionalUri));
t.Start();
//Func<string> function = new Func<string>(() => GetWowAuctionFileInfo(optionalUri));
//Task<string> tInfo = Task<string>.Factory.StartNew(() => GetWowAuctionFileInfo(optionalUri));
}
}
}
private async void GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
returnValue = ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
ReturnedData = returnValue;
}
private List<string> ConvertFileInfoToConsumableList(RealmJSFileCheck jsfc)
{
List<string> returnData = new List<string>();
if (jsfc.files.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("File URL: ");
sb.Append(jsfc.files[0].url);
returnData.Add(sb.ToString());
sb = new StringBuilder();
sb.AppendLine("Last Modified: ");
sb.Append(jsfc.files[0].lastModified);
returnData.Add(sb.ToString());
}
else
{
returnData.Add("No File Info Found");
}
return returnData;
}
更新 再次感谢大家的详细评论。我浏览了很多关于 Task 用法的文档,并在这个练习中学到了很多东西。我将@Johnathon 的答案标记为解决方案,因为它提供了我所要求的内容,并提供了非常有用的 link 以获取更多信息。
因为你不等结果。
在分配数据之前,您使用 ReturnedData
循环。
我认为您根本不需要创建新的 Task
。使 GetWowAuctionFileInfo
方法正确异步 returns Task
。
private async Task GetWowAuctionFileInfo(string auctionInfoUri)
{
// same code
}
将 StartTask
更改为 return Task
。因为我们在这里不等待结果,所以我们不需要异步 make 方法。
建议将此方法的名称更改为 LoadData
例如,这将提供有关此方法功能的更多信息。
public Task StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
return GetWowAuctionFileInfo(optionalUri) // this will return Task
}
}
// if validation fails - return completed task or throw exception
return Task.CompletedTask;
}
然后就可以在Button_Click
事件处理器中调用了
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
await w.StartTask("FileInfo", "yourUrl");
// This line will be executed only after asynchronous methods completes succesfully
// or exception will be thrown
foreach (string res in w.ReturnedData)
{
textBoxResults.Text += res;
}
}
您的 GetWowAuctionFileInfo
方法是一个异步方法,您等待其中的异步调用而没有 return 任务。一般来说,使用 async void
是不好的做法。相反,将您的 GetWowAuctionFileInfo
方法变成 async Task<List<string>> GetWowAuctionFileInfo
。这将使您等待 GetAsync
调用、解析数据,并将 return 集合发送给调用者,而无需使用 ReturnObject
.
private async Task<List<string>> GetWowAuctionFileInfo(string auctionInfoUri)
{
RealmJSFileCheck realmInfoObject;
List<string> returnValue = new List<string>();
try
{
using (HttpClient client = new HttpClient())
{
for (int attempt = 0; attempt < 3; attempt++)
{
var response = await client.GetAsync(auctionInfoUri);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
realmInfoObject = JsonConvert.DeserializeObject<RealmJSFileCheck>(content);
// You can just return the List<T> now.
return ConvertFileInfoToConsumableList(realmInfoObject);
//returnValue = realmInfoObject.files.ToString();
break;
}
}
}
}
catch (InvalidOperationException iOpEx)
{
//recieved this when an invalid uri was passed in
}
}
因为该方法最初是 async void
,您无法在 buttonTestJSFCHI_Click
中等待调用它。现在我们已经使它全部基于任务,您可以在事件处理程序中等待它。请注意,事件处理程序通常是唯一可以接受的地方 async void
。任何时候您负责方法的创建,并且不受合同约束(如事件处理程序),您应该始终 return 异步方法上的任务。
private async void buttonTestJSFCHI_Click(object sender, RoutedEventArgs e)
{
JSON_Worker w = new JSON_Worker();
List<string> results = await w.StartTask("FileInfo", "https://us.api.battle.net/wow/auction/data/medivh?locale=en_US&apikey=<guid>");
foreach (string res in results)
{
textBoxResults.Text += res;
}
}
public async Task<List<string>> StartTask(string TaskName, string optionalUri= "no_uri_passed")
{
if (TaskName == "FileInfo")
{
//Need to use a lamba expression to call a delegate with a parameter
if (!(optionalUri == "no_uri_passed"))
{
// Since the GetWowAuctionFileInfo now returns Task, we don't need to create a new one. Just await the Task given back to us, and return the given result.
return await GetWowAuctionFileInfo(optionalUri);
}
}
}
您在调试时看到预期结果的原因是调试会话足够慢,以至于异步操作及时完成,您的代码可以使用它。当 运行 应用程序在调试器之外时,它的运行速度快于异步操作可以完成的速度,从而阻止您查看数据。因此需要 await
整个异步调用堆栈,这样您就可以防止在该代码路径下进一步执行,直到您收到所有需要的数据。
Microsoft has a good write up 关于基于任务的编程,我会通读它以帮助您理解它。
编辑
澄清一下,当您在方法上 return 一个 Task<T>
时,您将在 await 时得到结果。例如:
List<string> result = await StartTask();
即使 StartTask
returns Task<List<string>>
,await
操作将等待 StartTask()
方法完成,然后解包来自Task<T>
对象并自动返回结果。所以不要让方法签名欺骗你,如果你 await
它,你将返回结果数据,而不是实际的 Task
本身。您无需手动从 Task
中提取数据。