原文地址: http://www.ben.geek.nz/2010/07/one-time-cached-p_w_picpaths-in-windows-phone-7/

 It’s trivially simple to put an Image control onto a page in Windows Phone 7 and point it at a url. Handily, the Image control will also cache that URL, and not request it again for the life of your application. But what if you’re like me and really, really want to save data traffic? I want that p_w_picpath to download once, ever. The only reason that Image control should generate data traffic would be if I reinstall my application.

 
Presenting the ImageCacher (say it like Terminator, in a James Earle Jones voice). This is little class takes a URL, and returns a BitmapImage, and is guaranteed to hit your URL once and only once. Example code is available at the end of this post. Note, throughout these examples I use a helper class around the IsolatedStorage methods.
 
First things first, whip up an ImageCacheConverter that we can apply to any Image bindings and make them call the cacher:
 
view sourceprint?
 
 
 
  
  1. public class ImageCacheConverter : IValueConverter 
  2.     public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
  3.     { 
  4.         return ImageCacher.GetCacheImage(value.ToString()); 
  5.     } 
  6.   
  7.     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
  8.     { 
  9.         throw new NotImplementedException(); 
  10.     } 
Add that value converter as a handy static reference in our App.xaml:
 
 
 
  
  1. <Application.Resources> 
  2.     <ImageCacherDemo:ImageCacheConverter x:Key="p_w_picpathCacheConverter" /> 
  3.  

 
  
  1. </Application.Resources> 
 
And then use the value converter wherever we bind an p_w_picpath URL to our Image controls:
 
 
 
  
  1. <Image Source="{Binding ElementName=ImageSource, Path=Text, 
  2.  

 
  
  1. Converter={StaticResource p_w_picpathCacheConverter}}" Width="200" /> 
So now the ImageCacher can go to work. GetCacheImage is where the magic starts. The first thing we do is convert our URL to a local filename, then check if that file exists. If it does, it means we’ve already cached the p_w_picpath, so just return it as is:
 
 
  
  1. if ( IsoStore.FileExists( cacheFile ) ) 
  2.     result.SetSource( IsoStore.StreamFileFromIsoStore( cacheFile ) ); 
  3. else 
  4.     CacheImageAsync(url, result); 
CacheImageAsync does what it says: it caches the p_w_picpath on a background thread. This way we can replicate the behaviour of the standard Image control. The UI doesn’t wait for the p_w_picpath to load: the p_w_picpath is blank when we first see it, then populates with the actual p_w_picpath from the web server once it has downloaded.
 
So we create an object to pass the p_w_picpath url and BitmapImage to the background thread (I really should use a dedicated class here, but KeyValuePair does the trick), then kick it off.
 
 
 
  
  1. public static void CacheImageAsync( string url, BitmapImage p_w_picpath ) 
  2.     var items = new KeyValuePair<string, BitmapImage>(url, p_w_picpath); 
  3.     var t = new Thread(GetImageSource); 
  4.     t.Start(items); 
Inside CacheImage there’s a number of things that happen, shown in the snippet below. We create a WaitHandle that we can trigger when the p_w_picpath download completes; we pass this and the destination filename through to the web callback when we trigger the download; then we wait for up to 5 seconds. Once the time is up (or the p_w_picpath is downloaded), we check for the cache file and set our p_w_picpath source.
 
 
 
  
  1. var waitHandle = new AutoResetEvent( false ); 
  2. var fileNameAndWaitHandle = new KeyValuePair<string, AutoResetEvent>( cacheFile, waitHandle ); 
  3.  
  4. var wc = new WebClient(); 
  5. wc.OpenReadCompleted += OpenReadCompleted; 
  6. // start the caching call (web async) 
  7. wc.OpenReadAsync( new Uri( url ), fileNameAndWaitHandle ); 
  8.  
  9. // wait for the file to be saved, or timeout after 5 seconds 
  10. waitHandle.WaitOne( 5000 ); 
  11. if ( IsoStore.FileExists(cacheFile) ) 
  12.     // ok, our file now exists! set the p_w_picpath source on the UI thread 
  13.     Deployment.Current.Dispatcher.BeginInvoke(() => p_w_picpath.SetSource(IsoStore.StreamFileFromIsoStore(cacheFile))); 
So in the web callback, it’s simply a matter of saving the streamed p_w_picpath download, then triggering the WaitHandle to let the CacheImage method know we’re done:
 
 
 
  
  1. var state = (KeyValuePair<string, AutoResetEvent>)e.UserState; 
  2. IsoStore.SaveToIsoStore( state.Key, e.Result ); 
  3. state.Value.Set(); 
It’s a fair bit of work, but the result is exactly what I wanted. If you start up NetMon, you’ll see one and only one request, even if you stop and restart the application. You’ll also notice that the p_w_picpath is significantly faster to load on the second and subsequent application loads.
 
There’s a few things to tidy up. Like if the cache file doesn’t exist after the 5 second timeout, we could display a “broken” p_w_picpath, or a default local p_w_picpath. We could also implement some sort of cache timeout so that the p_w_picpath is re-downloaded every week or month. I’ll leave those as an exercise for the reader.