diff --git includes/common.inc includes/common.inc
index 0e275fd..78579ab 100644
--- includes/common.inc
+++ includes/common.inc
@@ -3798,6 +3798,49 @@ function show(&$element) {
 }
 
 /**
+ * Helper for elements which use caching to avoid work during rendering.
+ * Typically called as a #pre_render callback.
+ *
+ * @see blog_block_view()
+ * 
+ * @param $elements
+ *   A render() array.
+ * @return
+ *   A render array() enriched with cache data or with a post_render() 
+ *   callback that will save to cache.
+ */
+function drupal_render_cache_get($elements) {
+  $bin = isset($elements['#cache_bin']) ? $elements['#cache_bin'] : 'cache';
+  if ($cache = cache_get($elements['#cache_key'], $bin)) {
+    // Cache hit - set the markup based on cached data.
+    $elements['#markup'] = $cache->data;
+    $elements['#theme'] = 'markup';
+  } 
+  else {
+    // Cache miss. Let rendering happen normally; then cache it.
+    $elements['#post_render'][] = 'drupal_render_cache_set';
+  }
+  return $elements;
+}
+
+/**
+ * Cache the rendered HTML. Typically called as a #post_render callback.
+ *
+ * @param $children
+ *   Rendered HTML.
+ * @param $elements
+ *   A render() array.
+ * @return
+ *   $children, unmodified.
+ */
+function drupal_render_cache_set($children, $elements) {
+  $bin = isset($elements['#cache_bin']) ? $elements['#cache_bin'] : 'cache';
+  $expire = isset($elements['#cache_expire']) ? $elements['#cache_expire'] : CACHE_PERMANENT;
+  cache_set($elements['#cache_key'], $children, $bin, $expire);
+  return $children;
+}
+
+/**
  * Function used by uasort to sort structured arrays by weight.
  */
 function element_sort($a, $b) {
diff --git modules/block/block.module modules/block/block.module
index c28ccb1..9da4a2a 100644
--- modules/block/block.module
+++ modules/block/block.module
@@ -18,7 +18,8 @@ define('BLOCK_REGION_NONE', -1);
  * combinations of these constants in their hook_block_list():
  *   $block[delta]['cache'] = BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE;
  * BLOCK_CACHE_PER_ROLE is used as a default when no caching pattern is
- * specified.
+ * specified. Use BLOCK_CACHE_CUSTOM to disable standard block cache and 
+ * implement 
  *
  * The block cache is cleared in cache_clear_all(), and uses the same clearing
  * policy than page cache (node, comment, user, taxonomy added or updated...).
@@ -38,6 +39,14 @@ define('BLOCK_REGION_NONE', -1);
 define('BLOCK_NO_CACHE', -1);
 
 /**
+ * The block is handling its own caching in its hook_block_view(). From the 
+ * perspective of the block cache system, this is equivalent to BLOCK_NO_CACHE.
+ * Useful when time based expiration is needed or a site uses a node access 
+ * which invalidates standard block cache.
+ */
+define('BLOCK_CACHE_CUSTOM', -2);
+
+/**
  * The block can change depending on the roles the user viewing the page belongs to.
  * This is the default setting, used when the block does not specify anything.
  */
@@ -703,7 +712,7 @@ function block_block_list_alter(&$blocks) {
  *   An array of block objects such as returned for one region by _block_load_blocks().
  *
  * @return
- *   An array of visible blocks with subject and content rendered.
+ *   An array of visible blocks as expected by drupal_render().
  */
 function _block_render_blocks($region_blocks) {
   foreach ($region_blocks as $key => $block) {
@@ -772,7 +781,7 @@ function _block_get_cache_id($block) {
   // it brings too many chances of having unwanted output get in the cache
   // and later be served to other users. We therefore exclude user 1 from
   // block caching.
-  if (variable_get('block_cache', 0) && $block->cache != BLOCK_NO_CACHE && $user->uid != 1) {
+  if (variable_get('block_cache', 0) && !in_array($block->cache, array(BLOCK_NO_CACHE, BLOCK_CACHE_CUSTOM)) && $user->uid != 1) {
     $cid_parts = array();
 
     // Start with common sub-patterns: block identification, theme, language.
diff --git modules/blog/blog.module modules/blog/blog.module
index 04c4d39..0f16c13 100644
--- modules/blog/blog.module
+++ modules/blog/blog.module
@@ -170,7 +170,10 @@ function _blog_post_exists($account) {
  * Implement hook_block_list().
  */
 function blog_block_list() {
-  $block['recent']['info'] = t('Recent blog posts');
+  $block['recent'] = array(
+    'info' => t('Recent blog posts'),
+    'cache' => BLOCK_CACHE_CUSTOM,
+  );
   return $block;
 }
 
@@ -180,24 +183,48 @@ function blog_block_list() {
  * Displays the most recent 10 blog titles.
  */
 function blog_block_view($delta = '') {
-  global $user;
-
-  if (user_access('access content')) {
-    $result = db_select('node', 'n')
-      ->fields('n', array('nid', 'title', 'created'))
-      ->condition('type', 'blog')
-      ->condition('status', 1)
-      ->orderBy('created', 'DESC')
-      ->range(0, 10)
-      ->addTag('node_access')
-      ->execute();
+  $query = db_select('node', 'n')
+    ->fields('n', array('nid', 'title', 'created'))
+    ->condition('type', 'blog')
+    ->condition('status', 1)
+    ->orderBy('created', 'DESC')
+    ->range(0, 10)
+    ->addTag('node_access');
+
+  // selectQuery class is not yet serializable. This is a robust workaround.
+  $key = md5(serialize(array((string)$query, $query->getArguments())));
+  
+  $block['subject'] = t('Recent blog posts');
+  // @todo - add user configurable time based expire.
+  $block['content'] = array(
+    '#access' => user_access('access content'),
+    '#pre_render' => array('drupal_render_cache_get', 'blog_block_view_pre_render'),
+    '#cache_key' => __FUNCTION__ . ":$key", 
+    '#cache_expire' => CACHE_TEMPORARY,
+    '#query' => $query,
+  );
+  return $block;
+}
 
+/**
+ * A pre_render callback. Lists recent blog posts or does nothing if 
+ * we have a cache hit.
+ *
+ * @return 
+ *   A $block array containing a subject and a render() array as content.
+ */
+function blog_block_view_pre_render($elements) {
+  if (isset($elements['#markup'])) {
+    // Cache hit. Nothing to do.
+  }
+  else {
+    // Cache miss. Execute query and build render() array. 
+    $result = $elements['#query']->execute();
     if ($node_title_list = node_title_list($result)) {
-      $block['content'] = $node_title_list;
-      $block['content'] .= theme('more_link', url('blog'), t('Read the latest blog entries.'));
-      $block['subject'] = t('Recent blog posts');
-      return $block;
+      $elements['blog_list'] = array('#markup' => $node_title_list);
+      $elements['blog_more'] = array('#markup' => theme('more_link', url('blog'), t('Read the latest blog entries.')));
     }
   }
+  return $elements;
 }
 
