Minsc Minsc - 10 days ago 6
C# Question

Async Function Not Getting Called

Please forgive me for any noobish mistakes seen below, I'm learning some of the concepts I'm attempting to work with.

Problem:
While debugging my app, I was able to call an

async
function with
Task.Start()
. I felt that the app was in a working state for the phase I'm in so removed all breakpoints with CTRL + SHIFT + F9.

Once I ran the app with no breakpoints it would fail due to a property not getting populated. Now when I try to debug any breakpoint I set in the
async
function that handles most of the work is longer hit. It's like it is getting skipped. Can anyone see a reason why
GetWowAuctionFileInfo
isn't being called?

GetWowAuctionFileInfo
is what is not getting called, or at least appears to be not getting called.

Thanks.

Relevant Code

Caller Function

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;
}
}


Called Functions

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;
}


UPDATE
Thanks again all for the detailed commentary. I've gone through much documentation regarding Task usage and learned a lot in this exercise. I'm marking the answer from @Johnathon as the solution because it provided exactly what I was asking for and provided a very helpful link for more information.

Answer

Your GetWowAuctionFileInfo method is an asynchronous method, and you await an async call within it without returning a Task. In general it is bad practice to use async void. Instead, turn your GetWowAuctionFileInfo method into async Task<List<string>> GetWowAuctionFileInfo. This will let you await the GetAsync call, parse the data, and return the collection to the caller without having to use a 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 
    }
}

Because the method was originally async void, you could not await the calling of it in your buttonTestJSFCHI_Click. Now that we've made it all Task based, you can await it within your event handler. Note that event handlers are generally the only acceptable place to use async void. Any time you are responsible for the creation of the methods, and not constrained by a contract (like event handlers), you should always return a Task on your async methods.

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);
        }
    }
}

The reason you saw the expected result while debugging is because the debug session was slow enough that the async operation completed in time for your code to use it. When running the app outside of the debugger, it runs faster than the async operation could complete, preventing you from seeing the data. Thus the need to await the entire async call stack, so you can prevent further execution from happening down that code-path until you receive all of the desired data.

Microsoft has a good write up on Task based programming, I'd take a read through it to help you understand it some.

EDIT

Just to clarify, when you return a Task<T> on your methods, you will be given the result when you await. For example:

List<string> result = await StartTask();

Even though StartTask returns Task<List<string>>, the await operation will wait for the StartTask() method to complete, and then unwrap the result from the Task<T> object and give you the result back automatically. So don't let the method signature fool you, if you await it, you will be given back the resulting data, and not the actual Task itself. There won't be any need for you to pull the data out of the Task manually.