Reputation: 122
I want to use a Portable Class Library in a Windows Phone 8 app. The PCL contains async methods only and that's fine. But I want to call one of the methods from the Application_Launching
event handler and wait for the result, so that I can instantly use it, as soon as the main page gets loaded.
These similar questions didn't help me:
For easy reproduction the following code is similar but more simple:
The PCL:
public class TestAsyncClass
{
public async Task<string> SendRequestAsync()
{
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync("http://www.google.com").ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}
}
As you can see, there's ConfigureAwait(false)
for the method calls that are being awaited, so there shouldn't be a deadlock because of some captured context that the method wants to return to. At least that's how I understood Stephen Cleary's blog article on that topic: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
The App.xaml.cs:
private async void Application_Launching(object sender, LaunchingEventArgs e)
{
TestAsyncLib.TestAsyncClass tac = new TestAsyncLib.TestAsyncClass();
//string result = await tac.SendRequestAsync(); // GlobalData.MyInitString is still empty in MainPage.xaml.cs OnNavigatedTo event handler
string result = tac.SendRequestAsync().Result; // Gets stuck
GlobalData.MyInitString = result;
}
As written in the comments, when calling the method asynchronously, GlobalData.MyInitString is still empty when trying to access it in the MainPage.xaml.cs OnNavigatedTo event handler, because the UI thread gets the focus immediately and launches the main page before the library method is able to return any results. And calling the method synchronously causes the library method to get stuck.
Here's the MainPage.xaml.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
System.Diagnostics.Debug.WriteLine(GlobalData.MyInitString);
}
For completeness, GlobalData.cs:
public static class GlobalData
{
public static string MyInitString { get; set; }
}
I'm thankful for any help!
Upvotes: 0
Views: 2515
Reputation: 122
I'm answering my own question now, but thanks to eX0du5 for pointing me to the right direction.
eX0du5 pointed out that void methods can't be awaited because they're not returning a Task. Searching for "Application_Launching returning Task" got me to a great msdn blog article that explained everything in detail:
The thing is: Event handlers can't return Tasks and the recommendation for calling async methods within app lifecycle event handlers is: "Don't."
Also, in the last paragraph of the blog article the author says that using .Wait()
on the async method might work, but it can deadlock when the UI thread gets the scheduler's attention.
So both issues I had with my two different approaches are explained. What to do now? eX0du5's suggestion (saving the Task instead of the data to the GlobalData
static class) is a workaround. In case the data that gets requested at the launch of the app needs to be accessed from different pages this seems like good solution, and in case the data is only needed on a page that doesn't get loaded after launching the app it even seems like a very good solution for prefetching data. I just checked that Tasks get executed when they get created and not only when awaited or used with .Result
or .Wait()
with this code.
For my purpose though, as I was only using the GlobalData
for using data in the MainPage.xaml.cs that gets loaded when launching the app, it seems to be less confusing and more concise to not use the Application_Launching
event handler alltogether and instead do everything in the OnNavigatedTo
event handler. That's where Noseratio's comment on my question comes in handy and also this question on SO: When should I load data in a Windows Phone 8 application? .
Upvotes: 0
Reputation: 896
You are not able to use the fetched value in your OnNavigatedTo
because OnNavigatedTo
is called in parallel to your async method which you started in Application_Launching
.
This is because Application_Launching has return type void
and void methods cannot be awaited - because they have nothing meaningful to return ;-)
That means the app is started and you can trigger some things to do.
Then the app determines which page to load and fires your OnNavigatedTo
method.
Now you can either trigger your async method there, or hold a global variable which you can access that holds the task status of your result and check against this one.
Let me give an example for that task stuff.
Consider your Application_Launched
method:
MyStaticClass.Result = tac.SendRequestAsync();
the data definition in MyStaticClass is:
public static Task<string> Result;
Now in your Phone page OnNavigatedTo:
if (MyStaticClass.Result != null)
{
string myResult = await MyStaticClass.Result;
}
Instead of a static class or property you may also use a singleton.
But keep in mind, the OnNavigatedTo
method is also a void method and your viewModel will already get read requests from the underlying UI even though your data load might not yet be finished.
Upvotes: 2