On parle Windows, C#, Apple, Android, Js, …

Mise en cache d’image dans l’isolated storage avec Windows XAML et SemaphoreSlim

 

Ecriture dans l’isolated Storage d’un fichier image

Ecriture Multiple dans l'Isolated Storage

Il est parfois fort utile de sauvegarder les images provenant d’un serveur HTTP dans un cache afin d’éviter de les charger à chaque fois.

En implémentant ce système de cache en asynchrone, je me suis rendu compte de plusieurs problèmes causant systématiquement un UnauthorizedAccessException: Access is denied (0x80070005).

ScreenShot Exception Access Denied
UnauthorizedAccessException: Access is denied (0x80070005).

Le problème rencontré intervient lorsque plusieurs images d’une liste essaient d’enregistrer des éléments dans l’Isolated Storage en même temps.

Pourtant le code me paraissait plutôt correct ?

public async Task<string> SaveImageInStorageAsyncBad(string urlImage, object datas)
{
    if (!String.IsNullOrEmpty(urlImage) && datas is byte[])
    {
        string id = urlImage;
       
            var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
            var folder = await localFolder.CreateFolderAsync("DataCache", Windows.Storage.CreationCollisionOption.OpenIfExists);
            var file = await folder.CreateFileAsync(id, Windows.Storage.CreationCollisionOption.ReplaceExisting);
            var stream = await file.OpenStreamForWriteAsync();
            using (var outStream = stream)
            {
                var dataWriter = new Windows.Storage.Streams.DataWriter(outStream.AsOutputStream());
                dataWriter.WriteBytes(datas as byte[]);
                await dataWriter.StoreAsync();
                return id;
            }
        
    }
    else
        return String.Empty;
}

 FlushAsync()

La première chose qu’il manque est un await dataWriter.FlushAsync() juste après le StoreAsync().

En effet, nous utilisons un Stream pour écrire l’image.

Le Flush permet de s’assurer que tout le stream va être écrit correctement avant de continuer.

Verrou – Lock ?

Mais le Flush ne suffit pas l’erreur est encore rencontrée. Il va falloir protéger le code en évitant une écriture parallèle des images.

En mode Synchrone un Lock aurait pû être utilisé mais nous somme dans un traitement asynchrone.

L’utilisation du Lock est interdite car empêche le thread de continuer. Le compilateur l’indique clairement.

Pour assurer une écriture à la fois, nous allons utiliser l’objet SemaphoreSlim.

Il faut initialiser cet objet de la façon suivante :

private SemaphoreSlim _syncLock = new SemaphoreSlim(1);

Le 1 en paramètre indique que nous ne souhaitons q’un seul accès concurent.

Pour blocker les accès concurent on appelle la méthode :

await _syncLock.WaitAsync();

Pour rendre le block

_syncLock.Release();

 

Le code final qui fonctionne sans erreur ressemble maintenant à ceci :

A noter  : l’utilisation du try … finally pour assurer le déverrouillage même en cas d’anomalies.

public async Task<string> SaveImageInStorageAsync(string urlImage, object datas)
{
    if (!String.IsNullOrEmpty(urlImage) && datas is byte[])
    {
        string id = GetIdStorage(urlImage, savedName);
        await _syncLock.WaitAsync();
        try
        {
            var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
            var folder = await localFolder.CreateFolderAsync("DataCache", Windows.Storage.CreationCollisionOption.OpenIfExists);
            var file = await folder.CreateFileAsync(id, Windows.Storage.CreationCollisionOption.ReplaceExisting);
            var stream = await file.OpenStreamForWriteAsync();
            using (var outStream = stream)
            {
                var dataWriter = new Windows.Storage.Streams.DataWriter(outStream.AsOutputStream());
                dataWriter.WriteBytes(datas as byte[]);
                await dataWriter.StoreAsync();
                await dataWriter.FlushAsync();
                outStream.Dispose();
                return id;
            }
        }
        finally
        {
            _syncLock.Release();
        }
    }
    else
        return String.Empty;
}

 

Voici donc un mécanisme fort utile pour améliorer la réactivité de vos applications Windows et Windows Phone

Laissez un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.