问题
I'm trying to port a simple app I had running on Windows Phone 8 to Universal Windows. I have a few files I need to load (locally) before constructing the main frame/page as those instruct how to create the page in the first place. There is a .XML and a few content files that are part of Assets.
I used to do the initialization during the App.Application_Launching() which I guess now is replaced by App.OnLaunched(). The trouble is with the new asynchronous-only file IO I can't seem to find a place where I can call any Async APIs without the program hanging. It appears that anywhere in App.OnLaunched(), MainPage.MainPage(), MainPage.OnNavigatedTo(), etc. I can't use await.
I have to basically fire off a background thread to schedule a task to run later to do the actual initialization, and then call back into the MainPage's pool to get something to run correctly. The resultant code looks something like below. Seems overly complex, but I guess this is correct?
Thanks, Nick.
public partial class MainPage : Page
{
// Constructor
public MainPage()
{
InitializeComponent();
// This doesn't work ...
// var task = InitAsync();
// task.Wait()
// This seems to ...
IAsyncAction asyncAction = ThreadPool.RunAsync((workItem) =>
{
var ignored = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
TryInitAsync();
});
});
}
private async void TryInitAsync()
{
try
{
await InitAsync();
}
catch
{
int foo = 0;
}
}
private async Task<Boolean> InitAsync()
{
// Things that go await in the night
// Other things that update the layout
return true;
}
}
回答1:
Seems overly complex, but I guess this is correct?
No, I wouldn't say so.
I can't offer specific advice about the initialization itself, since those details are missing from your question. The basic strategy is for the UI to tolerate uninitialized data, and to run the initialization asynchronously, updating properties as needed.
As far as your specific means of starting the asynchronous initialization goes, you have made it more complicated than it needs to or should be. This would be better:
public MainPage()
{
InitializeComponent();
var ignored = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow
.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => TryInitAsync());
}
I.e. there's no need to start a background task with ThreadPool.RunAsync()
if all you're going to do is defer your call to the TryInitAsync()
method, invoking it back on the UI thread.
Doing it this way (or your more complicated way), you are still blocking the UI thread though. It would be even better to not use Dispatcher.RunAsync()
. Instead, do call TryInitAsync()
directly from a background task (e.g. invoke it with ThreadPool.RunAsync()
) and have the UI-bound properties dependent on the initialization updated asynchronously as the initialization progresses.
回答2:
It appears that anywhere in App.OnLaunched(), MainPage.MainPage(), MainPage.OnNavigatedTo(), etc. I can't use await.
The core conceptual change is that modern applications must show some kind of UI immediately (which means synchronously). This is why you can't do I/O on startup.
So, you have to think about what your app should look like before it loads any files, show that state immediately, and also start asynchronous loading of your "real" UI.
You could just call TryInitAsync
directly from your constructor:
public MainPage()
{
InitializeComponent();
TryInitAsync();
}
Or, if you can update your UI with data binding, you could use my NotifyTask<T> type:
public NotifyTask<InitData> Data { get; }
public MainPage()
{
InitializeComponent();
Data = NotifyTask.Create(InitAsync());
}
private async Task<InitData> InitAsync() { ... }
And then you can data-bind to Data.Result
to get at InitData
, or Data.IsCompleted
/Data.IsNotCompleted
to show/hide the initial "loading..." status.
来源:https://stackoverflow.com/questions/40099349/universal-windows-c-await-during-app-initialization