Udostępnij za pośrednictwem


Beware the perils of async/await in application lifecycle event handlers (in fact in *any* event handlers)

Many developers are, quite rightly, adopting the use of async and await when writing Windows Phone and Windows 8 Store apps. It’s the right thing to do, as it allows long-running operations such as network calls and file i/o to complete efficiently without any risk of inadvertently locking up the UI thread and creating a poor user experience by freezing the screen responsiveness. In fact, in Windows 8 Store apps, it’s the *only* way to do these operations because in the .NET API for Windows Store apps, the old synchronous methods have been removed and only the new async versions are available. On Windows Phone 8, the developer has the choice of using the old Window Phone 7-style APIs, or in many cases newer async alternatives, such as the WinRT Windows.Storage APIs for file operations.

However, we’re seeing a lot of postings in the forums saying that their app only works intermittently, or works fine in debug but then fails intermittently when deployed. Quite often, the reason for this is that the developer has inadvertently called some async methods in a ‘fire and forget’ fashion, and what they are seeing is a synchronization problem; they assume that some async code has completed, and some code elsewhere is trying to use the result value from the async method before the async method has completed. The result, a null reference exception or incorrect behaviour because some object is not fully populated.

The problem with async/await in Application_Launching and Application_Activated

I accidently encountered this problem when I was building a simple demo for the last Windows Phone 8 JumpStart video series. OK, it was a dumb demo, but demos are often dumb because you want to keep them simple so as not to distract from the main point you are trying to make. This demo was to show how to program with the new Windows.Storage apis, so in my App class Application_Launching event handler I had code that read some data out of a file and saved the string into a public property called LogData:

     public partial class App : Application
    {
        ...

        // This is shared amongst all the pages
        // It is the contents of the log itself, as a large string
        public string LogData;

        // Code to execute when the application is launching (eg, from Start)
        // This code will not execute when the application is reactivated
        private async void Application_Launching(object sender, LaunchingEventArgs e)
        {            
            // Warning - this async method may not complete before this event handler 
            // method completes and other events fire. 
            this.LogData = await LoadFromLocalFolderAsync();
        }

        public async static Task<string> LoadFromLocalFolderAsync()
        {
            string theData = string.Empty;

            // There's no FileExists method in WinRT, so have to try to get a reference to it
            // and catch the exception instead
            StorageFile storageFile = null;
            bool fileExists = false;
            try
            {
                // See if file exists
                storageFile = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
                    new Uri("ms-appdata:///local/CaptainsLog.store"));
                fileExists = true;
            }
            catch (FileNotFoundException)
            {
                // File doesn't exist
                fileExists = false;
            }

            if (!fileExists)
            {
                // Initialise the return data
                theData = string.Empty;
            }
            else
            {
                // File does exist, so open it and read the contents
                Stream readStream = await storageFile.OpenStreamForReadAsync();
                using (StreamReader reader = new StreamReader(readStream))
                {
                    theData = await reader.ReadToEndAsync();
                }
            }

            return theData;
        }

And in MainPage.xaml.cs, OnNavigatedTo, the code gets the value of LogData from the App class and sets the Text property of a TextBlock to the string to show the contents of the file on the screen. Logic elsewhere in the app writes content into the file, so on startup you should always see the current contents of the file shown on the screen on application launch.

         protected override void  OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
             base.OnNavigatedTo(e);
            
            // When we navigate to the page - put the log text into the display
            completeLogTextBlock.Text = ((App)App.Current).LogData;
        }

What could possibly go wrong? Well, plenty as it turns out! The first time you run this app, the screen is empty which is to be expected because the file is empty. But then you use the app to enter some text which is written to the file, then close the app. Launch again, and where you would expect to see some text displayed, the screen is still empty! What gives?

Exploring execution of Async methods

To show why this went wrong, let’s look at a simple console app to explore the two ways you can execute an async method.

 using System;
using System.Threading.Tasks;

namespace AsyncSynchronizationProblem
{
    class Program
    {
        static void Main(string[] args)
        {
            new TheClassThatDoesWork().DoTheWork();

            Console.ReadKey();
        }
    }

    class TheClassThatDoesWork
    {
        public async void DoTheWork()
        {
            Console.WriteLine("TRACE Calling SomeLongProcedure Async");
            await SomeLongProcedureAsync();
            Console.WriteLine("TRACE Returned from SomeLongProcedure Async");

            Console.WriteLine();

            Console.WriteLine("TRACE Calling SomeLongProcedure Fire and forget");
            SomeLongProcedureAsync();
            Console.WriteLine("TRACE Returned SomeLongProcedure Fire and forget");
        }

        private Task SomeLongProcedureAsync()
        {
            var task = Task.Run(() =>
                {
                    Console.WriteLine("TRACE Starting SomeLongProcedure work");
                    System.Threading.Thread.Sleep(1000); // Simulate long running op
                    Console.WriteLine("TRACE Completed SomeLongProcedure work");
                });

            return task;
        }
    }
}

When you run this app, you see the following console output:

asyncdemo1

In this sample, the long running method, Task SomeLongProcedureAsync() simply sleeps for 1s to simulate executing some lengthy code. In the method DoTheWork() , we just call the async method twice, once using await and once without. We write to the console output to show what is happening. I’m sure it is no surprise to most of you, that when we call using await, the caller waits until the long-running work has completed before continuing execution itself:

TRACE Calling SomeLongProcedure Async

TRACE Starting SomeLongProcedure work

TRACE Completed SomeLongProcedure work

TRACE Returned from SomeLongProcedure Async

and when we call without using await, it works in a fire-and-forget fashion, and the caller continues execution without waiting for the task to complete:

TRACE Calling SomeLongProcedure Fire and forget

TRACE Starting SomeLongProcedure work

TRACE Returned from SomeLongProcedure Fire and forget

TRACE Completed SomeLongProcedure work

This is pretty basic stuff about calling async methods. So why is this relevant?

Dangers of making event handlers async

Let’s look at another example. This one is still a console app, but has four classes, Program which just hosts the Main method of the console app, App, PhoneApplicationService and MainPage – I have named the last three the same as key classes in a Windows Phone app for reasons which hopefully will become obvious.

 using System;
using System.Threading.Tasks;

namespace AsyncSynchronizationProblemWithEvents
{
    class Program
    {
        static void Main(string[] args)
        {
            var phoneAppService = new PhoneApplicationService();
            var app = new App(phoneAppService);
            
            phoneAppService.DoTheWork();

            // When the PhoneApplicationService has completed, construct a MainPage instance
            var mainPage = new MainPage();
            // Call methods on it
            mainPage.OnNavigatedTo();

            Console.ReadKey();
        }
    }

    class App
    {
        private PhoneApplicationService appService;

        public App(PhoneApplicationService applicationService)
        {
            appService = applicationService;
            // Subscribe to the PhoneApplicationService lifetime event(s)
            appService.ApplicationLaunching += Application_Launching;
        }

        async void Application_Launching(object sender, EventArgs e)
        {
            Console.WriteLine(
                "TRACE Event handler - Calling SomeLongProcedure Async");
            await SomeLongProcedureAsync();
            Console.WriteLine(
                "TRACE Event handler - Returned from SomeLongProcedure Async");
        }

        private Task SomeLongProcedureAsync()
        {
            var task = Task.Run(() =>
            {
                Console.WriteLine("TRACE Starting SomeLongProcedure work");
                System.Threading.Thread.Sleep(1000); // Simulate long running op
                Console.WriteLine("TRACE Completed SomeLongProcedure work");
            });

            return task;
        }
    }

    class PhoneApplicationService
    {
        public event EventHandler ApplicationLaunching;

        public void DoTheWork()
        {
            // Here we do some stuff...

            // Call any subscribers to our event(s)
            if (ApplicationLaunching != null)
            {
                ApplicationLaunching(this, new EventArgs());
            }
        }
    }

    class MainPage
    {
        public void OnNavigatedTo()
        {
            Console.WriteLine("TRACE Starting MainPage OnNavigatedTo");

            // Do some work here too...

            Console.WriteLine("TRACE Completed MainPage OnNavigatedTo");
        }
    }
}

If you study this app, you will see that the Main method creates instances of PhoneApplicationService, creates an instance of App and passes the PhoneApplicationService instance into its constructor, and finally creates an instance of MainPage and calls its OnNavigatedTo method. This is, of course, exactly what happens when you start a Windows Phone app. When you run it, the output looks like this:

asyncdemo2

There is clearly something odd going on here: the trace messages from the execution of SomeLongProcedure are output, but right in the middle, the calls to MainPage OnNavigatedTo appear. What’s going on?

Inside the App constructor, we hook the ApplicationLaunching event of the PhoneApplicationService instance so that in the event handler we can do some setup work when the app launches, and this of course is where we call the long-running async method:

         async void Application_Launching(object sender, EventArgs e)
        {
            Console.WriteLine(
                "TRACE Event handler - Calling SomeLongProcedure Async");
            await SomeLongProcedureAsync();
            Console.WriteLine(
                "TRACE Event handler - Returned from SomeLongProcedure Async");
        }

We are using the await keyword inside this event handler, so of course we mark it with the async modifier as normal. So at first glance, the developer expects this code to execute and for the event handler code to wait for completion of SomeLongProcedureAsync() before continuing execution, which in this case is simply to write a message to the console. If we look at the output, that is indeed what happens:

TRACE Event handler - Calling SomeLongProcedure Async TRACE Starting SomeLongProcedure work

TRACE Starting MainPage OnNavigatedTo

TRACE Completed MainPage OnNavigatedTo

TRACE Completed SomeLongProcedure work TRACE Event handler - Returned from SomeLongProcedure Async

But as you can see from the trace output, the MainPage OnNavigatedTo method is executed before the Application_Launching event handler has completed, even though it was clearly invoked by the Main function after the PhoneApplicationService.DoWork() method was invoked, which is the method that fires the Application_Launching event:

         static void Main(string[] args)
        {
            var phoneAppService = new PhoneApplicationService();
            var app = new App(phoneAppService);
            
            phoneAppService.DoTheWork();

            // When the PhoneApplicationService has completed, construct a MainPage
            var mainPage = new MainPage();
            // Call methods on it
            mainPage.OnNavigatedTo();

            Console.ReadKey();
        }

The problem of course, is that there is no way of awaiting the completion of an event handler, so when the DoWork() method fires the Application_Launching event, it does so in a fire and forget fashion, just the same as in the calling of SomeLongProcedureAsync() without using the await keyword, as we demonstrated in the first example. During execution, code executes sequentially until we hit the await keyword inside the Application_Launching event handler, at which point the wonders of the Task Parallel Library ensure that execution of the Task continues without blocking the original context and control returns immediately to the caller (PhoneApplicationService.DoTheWork() in this case). 

If the Application_Launching event handler had returned async Task instead of async void:  

async Task Application_Launching(object sender, EventArgs e)

(which is not possible with event handlers) and if it had been called using the await keyword, then  DoWork() would have awaited completion of the Application_Launching event handler logic before continuing, in which case, MainPage.OnNavigatedTo() would not have been called before the Application_Launching logic had completed. You can only await completion of methods that return Task or Task<T> and since this is impossible with event handlers, you can never await their completion. Hence there is every chance that your MainPage OnNavigatedTo logic will execute before your Application_Launching or Application_Activated logic has completed, although it is actually hit or miss, which is why so many developers report intermittent failures.

The Solution?

Of course, this is not a new problem. Any time you execute code asynchronously from your application lifecycle event handlers, whether using async/await or other asynchronous programming techniques, there is a real chance that you will have to implement some kind of synchronisation logic to ensure that your page logic does not try to access objects before your startup logic has set them up. Using older async coding technologies, this was perhaps a little more obvious to the developer, but many devs use the new Task-based async methods without thinking too deeply about it. It just works – or at least seems to until you hit a timing-related problem such as the one I’ve described here.

In the simple demo I described at the beginning of this article, the solution was simple. Instead of exposing the data fro the app in a public property of the App class, I created a new ViewModel class to host it and used XAML databinding to bind the TextBlock on MainPage to a property of the ViewModel. When the Application_Launching logic completed, it updated the ViewModel instance, and through the wonders of INotifyPropertyChanged, the MainPage UI updated with the correct data.

For other solutions, you may need to implement some other mechanism. One example was shown me by Peter Torr of the Windows Phone Product Group and is shown in the sample app which you can download from my post on SQLite:  SQLite-WinRT: Database programming on Windows Phone and Windows 8 . That sample opens (and the first time, creates) the SQLite database in Application_Launching and when resuming from tombstoning in Application_Activated. That sample used an old-fashioned synchronisation primitive, a ManualResetEvent, as a flag that is only set when the application lifecycle logic has finished:

   public partial class App : Application
  {
    static SQLiteWinRTPhone.Database db;
    static ManualResetEvent DBLoaded = new ManualResetEvent(false);

    public static Task<SQLiteWinRTPhone.Database> GetDatabaseAsync()
    {
      return Task.Run(() =>
      {
        DBLoaded.WaitOne();
        return db;
      });
    }

    // Code to execute when the application is launching (eg, from Start)
    // This code will not execute when the application is reactivated
    private void Application_Launching(object sender, LaunchingEventArgs e)
    {
      LoadDatabase();
    }

    private async Task LoadDatabase()
    {
      // Get a reference to the SQLite database
      db = new SQLiteWinRTPhone.Database(
          ApplicationData.Current.LocalFolder, "sqlitedemo.db");

      await db.OpenAsync();

      string sql = @"CREATE TABLE IF NOT EXISTS
                                Customer (Id      INTEGER PRIMARY KEY,
                                          Name    VARCHAR( 140 ),
                                          City    VARCHAR( 140 ),
                                          Contact VARCHAR( 140 ) 
                            );";
      string description = "Create Customer table";
      await db.ExecuteStatementAsync(sql);
        

      DBLoaded.Set();
    }
}

The ManualResetEvent is declared unsignalled as a static field of the app class:

 static ManualResetEvent DBLoaded = new ManualResetEvent(false);

and is only set at the very end of the async Task LoadDatabase() method:

private async Task LoadDatabase()

{

// Get a reference to the SQLite database

db = new SQLiteWinRTPhone.Database(…);

  await db.OpenAsync();

  ...

  DBLoaded.Set();

}

The final key ingredient is a ‘gatekeeper’ method which must be called by every other method in the app that wants to access the database:

public static Task<SQLiteWinRTPhone.Database> GetDatabaseAsync()

{

return Task.Run(() =>

{

DBLoaded.WaitOne();

return db;

});

}

This method waits for the DBLoaded semaphore to be set and does so on a Threadpool thread (through Task.Run()) so does not block the UI thread.

Calling async code from Application_Deactivated or Application_Closing

The guidance here is “don’t'”. If you write your apps carefully, you can be saving changes to persistent data as you go along, so you shouldn’t have anything to do in the application lifecycle events.

If you must, you can try doing something like this:

SomeAsyncMethod().AsTask().Wait()   

If the operation completes within the timeout period AND it doesn’t deadlock due to needing to pump the UI thread, it will work… but don’t count on it.

Sample code

Comments

  • Anonymous
    July 31, 2013
    I'm not convinced by your SQLLite solution - aren't you just blocking a thread waiting for the task to complete? Plus, I believe the ManualResetEvent requires an expensive switch to kernel-mode.I'd be inclined to use a TaskCompletionSource instead:static readonly TaskCompletionSource<bool> DBLoaded = new TaskCompletionSource<bool>();public static async Task<SQLiteWinRTPhone.Database> GetDatabaseAsync(){  await DBLoaded;  return db;}private async Task LoadDatabase(){  ...  DBLoaded.SetResult(true);}Alternatively, you could use Stephen Toub's AsyncManualResetEvent:blogs.msdn.com/.../10266920.aspx