diff --git a/backends/key-file/kf-backend.vala b/backends/key-file/kf-backend.vala
index 3da0d23..5c5ee76 100644
--- a/backends/key-file/kf-backend.vala
+++ b/backends/key-file/kf-backend.vala
@@ -31,6 +31,18 @@ using Folks.Backends.Kf;
  */
 public class Folks.Backends.Kf.Backend : Folks.Backend
 {
+  private bool _is_prepared = false;
+
+  /**
+   * Whether this Backend has been prepared.
+   *
+   * See {@link Folks.Backend.is_prepared}.
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
   /**
    * {@inheritDoc}
    */
@@ -57,31 +69,43 @@ public class Folks.Backends.Kf.Backend : Folks.Backend
    */
   public override async void prepare () throws GLib.Error
     {
-      File file;
-      string path = Environment.get_variable ("FOLKS_BACKEND_KEY_FILE_PATH");
-      if (path == null)
+      lock (this._is_prepared)
         {
-          file = File.new_for_path (Environment.get_user_data_dir ());
-          file = file.get_child ("folks");
-          file = file.get_child ("relationships.ini");
+          if (!this._is_prepared)
+            {
+              File file;
+              string path = Environment.get_variable (
+                  "FOLKS_BACKEND_KEY_FILE_PATH");
+              if (path == null)
+                {
+                  file = File.new_for_path (Environment.get_user_data_dir ());
+                  file = file.get_child ("folks");
+                  file = file.get_child ("relationships.ini");
 
-          debug ("Using built-in key file '%s' (override with environment " +
-              "variable FOLKS_BACKEND_KEY_FILE_PATH)", file.get_path ());
-        }
-      else
-        {
-          file = File.new_for_path (path);
-          debug ("Using environment variable FOLKS_BACKEND_KEY_FILE_PATH = '%s'"
-              + " to load the key file.", path);
-        }
+                  debug ("Using built-in key file '%s' (override with " +
+                      "environment variable FOLKS_BACKEND_KEY_FILE_PATH)",
+                      file.get_path ());
+                }
+              else
+                {
+                  file = File.new_for_path (path);
+                  debug ("Using environment variable " +
+                      "FOLKS_BACKEND_KEY_FILE_PATH = '%s' to load the key " +
+                      "file.", path);
+                }
 
-      /* Create the PersonaStore for the key file */
-      PersonaStore store = new Kf.PersonaStore (file);
+              /* Create the PersonaStore for the key file */
+              PersonaStore store = new Kf.PersonaStore (file);
 
-      this.persona_stores.insert (store.id, store);
-      store.removed.connect (this.store_removed_cb);
+              this.persona_stores.insert (store.id, store);
+              store.removed.connect (this.store_removed_cb);
 
-      this.persona_store_added (store);
+              this.persona_store_added (store);
+
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
+        }
     }
 
   private void store_removed_cb (Folks.PersonaStore store)
diff --git a/backends/key-file/kf-persona-store.vala b/backends/key-file/kf-persona-store.vala
index 6077d4b..1f9d06f 100644
--- a/backends/key-file/kf-persona-store.vala
+++ b/backends/key-file/kf-persona-store.vala
@@ -36,6 +36,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
   private GLib.KeyFile key_file;
   private uint first_unused_id = 0;
   private unowned Cancellable save_key_file_cancellable = null;
+  private bool _is_prepared = false;
 
   /**
    * {@inheritDoc}
@@ -53,6 +54,16 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
   public override string id { get; private set; }
 
   /**
+   * Whether this PersonaStore has been prepared.
+   *
+   * See {@link Folks.PersonaStore.is_prepared}.
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
    * {@inheritDoc}
    */
   public override HashTable<string, Persona> personas
@@ -81,98 +92,111 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
    */
   public override async void prepare ()
     {
-      string filename = this.file.get_path ();
-      this.key_file = new GLib.KeyFile ();
-
-      /* Load or create the file */
-      while (true)
+      lock (this._is_prepared)
         {
-          /* Load the file; if this fails due to the file not existing or having
-           * been deleted in the meantime, we can continue below and try to
-           * create it instead. */
-          try
+          if (!this._is_prepared)
             {
-              string contents = null;
-              size_t length = 0;
+              string filename = this.file.get_path ();
+              this.key_file = new GLib.KeyFile ();
 
-              yield this.file.load_contents_async (null, out contents,
-                  out length);
-              if (length > 0)
+              /* Load or create the file */
+              while (true)
                 {
-                  this.key_file.load_from_data (contents, length,
-                      KeyFileFlags.KEEP_COMMENTS);
+                  /* Load the file; if this fails due to the file not existing
+                   * or having been deleted in the meantime, we can continue
+                   * below and try to create it instead. */
+                  try
+                    {
+                      string contents = null;
+                      size_t length = 0;
+
+                      yield this.file.load_contents_async (null, out contents,
+                          out length);
+                      if (length > 0)
+                        {
+                          this.key_file.load_from_data (contents, length,
+                              KeyFileFlags.KEEP_COMMENTS);
+                        }
+                      break;
+                    }
+                  catch (Error e1)
+                    {
+                      if (!(e1 is IOError.NOT_FOUND))
+                        {
+                          warning ("The relationship key file '%s' could " +
+                              "not be loaded: %s", filename, e1.message);
+                          this.removed ();
+                          return;
+                        }
+                    }
+
+                  /* Ensure the parent directory tree exists for the new file */
+                  File parent_dir = this.file.get_parent ();
+
+                  try
+                    {
+                      /* Recursively create the directory */
+                      parent_dir.make_directory_with_parents ();
+                    }
+                  catch (Error e3)
+                    {
+                      if (!(e3 is IOError.EXISTS))
+                        {
+                          warning ("The relationship key file directory " +
+                              "'%s' could not be created: %s",
+                              parent_dir.get_path (), e3.message);
+                          this.removed ();
+                          return;
+                        }
+                    }
+
+                  /* Create a new file; if this fails due to the file having
+                   * been created in the meantime, we can loop back round and
+                   * try and load it. */
+                  try
+                    {
+                      /* Create the file */
+                      FileOutputStream stream = yield this.file.create_async (
+                          FileCreateFlags.PRIVATE, Priority.DEFAULT);
+                      yield stream.close_async (Priority.DEFAULT);
+                    }
+                  catch (Error e2)
+                    {
+                      if (!(e2 is IOError.EXISTS))
+                        {
+                          warning ("The relationship key file '%s' could " +
+                              "not be created: %s", filename, e2.message);
+                          this.removed ();
+                          return;
+                        }
+                    }
                 }
-              break;
-            }
-          catch (Error e1)
-            {
-              if (!(e1 is IOError.NOT_FOUND))
-                {
-                  warning ("The relationship key file '%s' could not be " +
-                      "loaded: %s", filename, e1.message);
-                  this.removed ();
-                  return;
-                }
-            }
 
-          /* Ensure the parent directory tree exists for the new file */
-          File parent_dir = this.file.get_parent ();
-
-          try
-            {
-              /* Recursively create the directory */
-              parent_dir.make_directory_with_parents ();
-            }
-          catch (Error e3)
-            {
-              if (!(e3 is IOError.EXISTS))
+              /* We've loaded or created a key file by now, so cycle through the
+               * groups: each group is a persona which we have to create and
+               * emit */
+              string[] groups = this.key_file.get_groups ();
+              foreach (string persona_id in groups)
                 {
-                  warning ("The relationship key file directory '%s' could " +
-                      "not be created: %s", parent_dir.get_path (), e3.message);
-                  this.removed ();
-                  return;
+                  if (persona_id.to_int () == this.first_unused_id)
+                    this.first_unused_id++;
+
+                  Persona persona = new Kf.Persona (this.key_file, persona_id,
+                      this);
+                  this._personas.insert (persona.iid, persona);
                 }
-            }
 
-          /* Create a new file; if this fails due to the file having been
-           * created in the meantime, we can loop back round and try and load
-           * it. */
-          try
-            {
-              /* Create the file */
-              FileOutputStream stream = yield this.file.create_async (
-                  FileCreateFlags.PRIVATE, Priority.DEFAULT);
-              yield stream.close_async (Priority.DEFAULT);
-            }
-          catch (Error e2)
-            {
-              if (!(e2 is IOError.EXISTS))
+              if (this._personas.size () > 0)
                 {
-                  warning ("The relationship key file '%s' could not be " +
-                      "created: %s", filename, e2.message);
-                  this.removed ();
-                  return;
+                  /* FIXME: Groupable.ChangeReason is not the right enum to use
+                   * here */
+                  this.personas_changed (this._personas.get_values (), null,
+                      null, null, Groupable.ChangeReason.NONE);
                 }
-            }
-        }
-
-      /* We've loaded or created a key file by now, so cycle through the groups:
-       * each group is a persona which we have to create and emit */
-      string[] groups = this.key_file.get_groups ();
-      foreach (string persona_id in groups)
-        {
-          if (persona_id.to_int () == this.first_unused_id)
-            this.first_unused_id++;
 
-          Persona persona = new Kf.Persona (this.key_file, persona_id, this);
-          this._personas.insert (persona.iid, persona);
-        }
-
-      if (this._personas.size () > 0)
-        {
-          /* FIXME: Groupable.ChangeReason is not the right enum to use here */
-          this.personas_changed (this._personas.get_values (), null, null, null,
-              Groupable.ChangeReason.NONE);
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
         }
     }
 
diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala
index 0dedb92..3f397bb 100644
--- a/backends/telepathy/lib/tpf-persona-store.vala
+++ b/backends/telepathy/lib/tpf-persona-store.vala
@@ -56,6 +56,7 @@ public class Tpf.PersonaStore : Folks.PersonaStore
   private AccountManager account_manager;
   private Logger logger;
   private Contact self_contact;
+  private bool _is_prepared = false;
 
   internal signal void group_members_changed (string group,
       GLib.List<Persona>? added, GLib.List<Persona>? removed);
@@ -92,6 +93,16 @@ public class Tpf.PersonaStore : Folks.PersonaStore
   public override string id { get; private set; }
 
   /**
+   * Whether this PersonaStore has been prepared.
+   *
+   * See {@link Folks.PersonaStore.is_prepared}.
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
    * The {@link Persona}s exposed by this PersonaStore.
    *
    * See {@link Folks.PersonaStore.personas}.
@@ -155,64 +166,76 @@ public class Tpf.PersonaStore : Folks.PersonaStore
    */
   public override async void prepare ()
     {
-      this.account_manager = AccountManager.dup ();
-
-      this.account_manager.account_disabled.connect ((a) =>
-        {
-          if (this.account == a)
-            {
-              this.personas_changed (null, this._personas.get_values (), null,
-                  null, 0);
-              this.removed ();
-            }
-        });
-      this.account_manager.account_removed.connect ((a) =>
+      lock (this._is_prepared)
         {
-          if (this.account == a)
+          if (!this._is_prepared)
             {
-              this.personas_changed (null, this._personas.get_values (), null,
-                  null, 0);
-              this.removed ();
-            }
-        });
-      this.account_manager.account_validity_changed.connect ((a, valid) =>
-        {
-          if (!valid && this.account == a)
-            {
-              this.personas_changed (null, this._personas.get_values (), null,
-                  null, 0);
-              this.removed ();
-            }
-        });
+              this.account_manager = AccountManager.dup ();
 
-      this.account.status_changed.connect (this.account_status_changed_cb);
+              this.account_manager.account_disabled.connect ((a) =>
+                {
+                  if (this.account == a)
+                    {
+                      this.personas_changed (null, this._personas.get_values (),
+                        null, null, 0);
+                      this.removed ();
+                    }
+                });
+              this.account_manager.account_removed.connect ((a) =>
+                {
+                  if (this.account == a)
+                    {
+                      this.personas_changed (null, this._personas.get_values (),
+                        null, null, 0);
+                      this.removed ();
+                    }
+                });
+              this.account_manager.account_validity_changed.connect (
+                  (a, valid) =>
+                    {
+                      if (!valid && this.account == a)
+                        {
+                          this.personas_changed (null, this._personas.get_values
+                            (), null, null, 0);
+                          this.removed ();
+                        }
+                    });
+
+              this.account.status_changed.connect (
+                  this.account_status_changed_cb);
+
+              TelepathyGLib.ConnectionStatusReason reason;
+              var status = this.account.get_connection_status (out reason);
+              /* immediately handle accounts which are not currently being
+               * disconnected */
+              if (status != TelepathyGLib.ConnectionStatus.DISCONNECTED)
+                {
+                  this.account_status_changed_cb (
+                      TelepathyGLib.ConnectionStatus.DISCONNECTED, status,
+                      reason, null, null);
+                }
 
-      TelepathyGLib.ConnectionStatusReason reason;
-      var status = this.account.get_connection_status (out reason);
-      /* immediately handle accounts which are not currently being disconnected
-       */
-      if (status != TelepathyGLib.ConnectionStatus.DISCONNECTED)
-        {
-          this.account_status_changed_cb (
-              TelepathyGLib.ConnectionStatus.DISCONNECTED, status, reason, null,
-              null);
-        }
+              try
+                {
+                  this.logger = new Logger (this.id);
+                  this.logger.invalidated.connect (() =>
+                    {
+                      warning ("lost connection to the telepathy-logger " +
+                        "service");
+                      this.logger = null;
+                    });
+                  this.logger.favourite_contacts_changed.connect (
+                      this.favourite_contacts_changed_cb);
+                }
+              catch (DBus.Error e)
+                {
+                  warning ("couldn't connect to the telepathy-logger service");
+                  this.logger = null;
+                }
 
-      try
-        {
-          this.logger = new Logger (this.id);
-          this.logger.invalidated.connect (() =>
-            {
-              warning ("lost connection to the telepathy-logger service");
-              this.logger = null;
-            });
-          this.logger.favourite_contacts_changed.connect (
-              this.favourite_contacts_changed_cb);
-        }
-      catch (DBus.Error e)
-        {
-          warning ("couldn't connect to the telepathy-logger service");
-          this.logger = null;
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
         }
     }
 
diff --git a/backends/telepathy/tp-backend.vala b/backends/telepathy/tp-backend.vala
index d1536f8..3c27517 100644
--- a/backends/telepathy/tp-backend.vala
+++ b/backends/telepathy/tp-backend.vala
@@ -30,6 +30,7 @@ using Folks.Backends.Tp;
 public class Folks.Backends.Tp.Backend : Folks.Backend
 {
   private AccountManager account_manager;
+  private bool _is_prepared = false;
 
   /**
    * {@inheritDoc}
@@ -53,23 +54,45 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
     }
 
   /**
+   * Whether this Backend has been prepared.
+   *
+   * See {@link Folks.Backend.is_prepared}.
+   */
+  public override bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
+
+  /**
    * {@inheritDoc}
    */
   public override async void prepare () throws GLib.Error
     {
-      this.account_manager = AccountManager.dup ();
-      yield this.account_manager.prepare_async (null);
-      this.account_manager.account_enabled.connect (this.account_enabled_cb);
-      this.account_manager.account_validity_changed.connect ((a, valid) =>
+      lock (this._is_prepared)
         {
-          if (valid)
-            this.account_enabled_cb (a);
-        });
+          if (!this._is_prepared)
+            {
+              this.account_manager = AccountManager.dup ();
+              yield this.account_manager.prepare_async (null);
+              this.account_manager.account_enabled.connect (
+                  this.account_enabled_cb);
+              this.account_manager.account_validity_changed.connect (
+                  (a, valid) =>
+                    {
+                      if (valid)
+                        this.account_enabled_cb (a);
+                    });
 
-      GLib.List<unowned Account> accounts = this.account_manager.get_valid_accounts ();
-      foreach (Account account in accounts)
-        {
-          this.account_enabled_cb (account);
+              GLib.List<unowned Account> accounts =
+                  this.account_manager.get_valid_accounts ();
+              foreach (Account account in accounts)
+                {
+                  this.account_enabled_cb (account);
+                }
+
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
         }
     }
 
diff --git a/folks/backend-store.vala b/folks/backend-store.vala
index 2264e6c..5e34e75 100644
--- a/folks/backend-store.vala
+++ b/folks/backend-store.vala
@@ -40,6 +40,7 @@ public class Folks.BackendStore : Object {
   private HashMap<string,Backend> _prepared_backends;
   private GLib.List<ModuleFinalizeFunc> finalize_funcs = null;
   private static weak BackendStore instance;
+  private static bool _backends_loaded = false;
 
   /**
    * Emitted when a backend has been added to the BackendStore.
@@ -112,6 +113,10 @@ public class Folks.BackendStore : Object {
       foreach (ModuleFinalizeFunc func in this.finalize_funcs)
         func (this);
 
+      /* reset status of backends */
+      lock (this._backends_loaded)
+        this._backends_loaded = false;
+
       /* manually clear the singleton instance */
       instance = null;
     }
@@ -123,49 +128,58 @@ public class Folks.BackendStore : Object {
    * environment variable, if it's set. If it's not set, backends will be
    * searched for in a path set at compilation time.
    */
-  public async void load_backends () throws GLib.Error {
-      assert (Module.supported());
-
-      var path = Environment.get_variable ("FOLKS_BACKEND_DIR");
-      if (path == null)
-        {
-          path = BuildConf.BACKEND_DIR;
-
-          debug ("Using built-in backend dir '%s' (override with environment "
-              + "variable FOLKS_BACKEND_DIR)", path);
-        }
-      else
-        {
-          debug ("Using environment variable FOLKS_BACKEND_DIR = '%s' to look "
-              + "for backends", path);
-        }
-
-      File dir = File.new_for_path (path);
-      assert (dir != null && yield is_dir (dir));
-
-      yield this.load_modules_from_dir (dir);
-
-      /* all the found modules should call add_backend() for their backends
-       * (adding them to the backend hash) as a consequence of
-       * load_modules_from_dir(). */
-
-      foreach (var backend in this.backend_hash.values)
+  public async void load_backends () throws GLib.Error
+    {
+      lock (this._backends_loaded)
         {
-          try
+          if (!this._backends_loaded)
             {
-              yield backend.prepare ();
-
-              debug ("New backend '%s' prepared", backend.name);
-              this._prepared_backends.set (backend.name, backend);
-              this.backend_available (backend);
-            }
-          catch (GLib.Error e)
-            {
-              warning ("Error preparing Backend '%s': %s", backend.name,
-                  e.message);
+              assert (Module.supported());
+
+              var path = Environment.get_variable ("FOLKS_BACKEND_DIR");
+              if (path == null)
+                {
+                  path = BuildConf.BACKEND_DIR;
+
+                  debug ("Using built-in backend dir '%s' (override with " +
+                      "environment variable FOLKS_BACKEND_DIR)", path);
+                }
+              else
+                {
+                  debug ("Using environment variable FOLKS_BACKEND_DIR = " +
+                      "'%s' to look for backends", path);
+                }
+
+              File dir = File.new_for_path (path);
+              assert (dir != null && yield is_dir (dir));
+
+              yield this.load_modules_from_dir (dir);
+
+              /* all the found modules should call add_backend() for their
+               * backends (adding them to the backend hash) as a consequence of
+               * load_modules_from_dir(). */
+
+              foreach (var backend in this.backend_hash.values)
+                {
+                  try
+                    {
+                      yield backend.prepare ();
+
+                      debug ("New backend '%s' prepared", backend.name);
+                      this._prepared_backends.set (backend.name, backend);
+                      this.backend_available (backend);
+                    }
+                  catch (GLib.Error e)
+                    {
+                      warning ("Error preparing Backend '%s': %s", backend.name,
+                          e.message);
+                    }
+                }
+
+              this._backends_loaded = true;
             }
         }
-  }
+    }
 
   /**
    * Add a new {@link Backend} to the BackendStore.
diff --git a/folks/backend.vala b/folks/backend.vala
index 5565b48..696a00c 100644
--- a/folks/backend.vala
+++ b/folks/backend.vala
@@ -35,6 +35,14 @@ using GLib;
 public abstract class Folks.Backend : Object
 {
   /**
+   * Whether {@link Backend.prepare} has successfully completed for this
+   * backend.
+   *
+   * @since 0.3.0
+   */
+  public abstract bool is_prepared { get; default = false; }
+
+  /**
    * A unique name for the backend.
    *
    * This will be used to identify the backend, and should also be used as the
@@ -91,6 +99,8 @@ public abstract class Folks.Backend : Object
    *
    * If this function throws an error, the Backend will not be functional.
    *
+   * This function is guaranteed to be idempotent (since version 0.3.0).
+   *
    * @since 0.1.11
    */
   public abstract async void prepare () throws GLib.Error;
diff --git a/folks/individual-aggregator.vala b/folks/individual-aggregator.vala
index ee1bef1..eaa3836 100644
--- a/folks/individual-aggregator.vala
+++ b/folks/individual-aggregator.vala
@@ -65,6 +65,18 @@ public class Folks.IndividualAggregator : Object
   private HashSet<Backend> backends;
   private HashTable<string, Individual> link_map;
   private bool linking_enabled = true;
+  private bool _is_prepared = false;
+
+  /**
+   * Whether {@link IndividualAggregator.prepare} has successfully completed for
+   * this aggregator.
+   *
+   * @since 0.3.0
+   */
+  public bool is_prepared
+    {
+      get { return this._is_prepared; }
+    }
 
   /**
    * A table mapping {@link Individual.id}s to their {@link Individual}s.
@@ -153,6 +165,8 @@ public class Folks.IndividualAggregator : Object
    * condition could occur, with the signal being emitted before your code has
    * connected to them, and {@link Individual}s getting "lost" as a result.
    *
+   * This function is guaranteed to be idempotent (since version 0.3.0).
+   *
    * @since 0.1.11
    */
   public async void prepare () throws GLib.Error
@@ -160,7 +174,16 @@ public class Folks.IndividualAggregator : Object
       /* Once this async function returns, all the {@link Backend}s will have
        * been prepared (though no {@link PersonaStore}s are guaranteed to be
        * available yet). This last guarantee is new as of version 0.2.0. */
-      yield this.backend_store.load_backends ();
+
+      lock (this._is_prepared)
+        {
+          if (!this._is_prepared)
+            {
+              yield this.backend_store.load_backends ();
+              this._is_prepared = true;
+              this.notify_property ("is-prepared");
+            }
+        }
     }
 
   private async void add_backend (Backend backend)
diff --git a/folks/persona-store.vala b/folks/persona-store.vala
index 2d865ef..a04c60c 100644
--- a/folks/persona-store.vala
+++ b/folks/persona-store.vala
@@ -175,6 +175,14 @@ public abstract class Folks.PersonaStore : Object
    */
   public abstract HashTable<string, Persona> personas { get; }
 
+  /**
+   * Whether {@link PersonaStore.prepare} has successfully completed for this
+   * store.
+   *
+   * @since 0.3.0
+   */
+  public abstract bool is_prepared { get; default = false; }
+
    /**
    * Whether the PersonaStore is writeable.
    *
@@ -216,6 +224,8 @@ public abstract class Folks.PersonaStore : Object
    *
    * If this function throws an error, the PersonaStore will not be functional.
    *
+   * This function is guaranteed to be idempotent (since version 0.3.0).
+   *
    * @since 0.1.11
    */
   public abstract async void prepare () throws GLib.Error;
