异步函数未被调用

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 中提取数据。