Simplified several functions by replacing task continuations with async/await
authorPanagiotis Kanavos <pkanavos@gmail.com>
Thu, 5 Jan 2012 20:10:18 +0000 (22:10 +0200)
committerPanagiotis Kanavos <pkanavos@gmail.com>
Thu, 5 Jan 2012 20:10:18 +0000 (22:10 +0200)
trunk/Pithos.Client.WPF/Shell/ShellView.xaml
trunk/Pithos.Core/Agents/BlockUpdater.cs
trunk/Pithos.Core/Agents/FileInfoExtensions.cs
trunk/Pithos.Core/Agents/NetworkAgent.cs
trunk/Pithos.Network/Signature.cs
trunk/Pithos.Network/TreeHash.cs

index d9cc8f2..9d277d0 100644 (file)
                         >
             <tb:TaskbarIcon.ContextMenu>
                 <ContextMenu x:Name="TaskbarMenu" >
-                    <MenuItem Header="{Binding OpenFolderCaption}" IsEnabled="{Binding HasAccounts}" x:Name="OpenPithosFolder" ItemsSource="{Binding Accounts}" >
+                    <MenuItem Header="{Binding OpenFolderCaption}" IsEnabled="{Binding HasAccounts}" x:Name="OpenPithosFolder" ItemsSource="{Binding Accounts}" >                        
                         <MenuItem.ItemTemplate>
                             <DataTemplate>
                                 <TextBlock x:Name="AccountLink"  Text="{Binding Path=UserName}" cal:Message.Attach="[Event MouseLeftButtonUp]=[Action OpenPithosFolder($dataContext)]" 
                                            cal:Action.TargetWithoutContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=MenuItem, AncestorLevel=2}, Path=DataContext}" />
                             </DataTemplate>
-                        </MenuItem.ItemTemplate>
+                        </MenuItem.ItemTemplate>                        
                         <MenuItem.Icon>
                             <Image Source="/Pithos.Client.WPF;component/Images/Folder.ico" />
                         </MenuItem.Icon>
index 91d8b97..f882db9 100644 (file)
@@ -243,21 +243,7 @@ namespace Pithos.Core.Agents
             return FileAsync.WriteAllBytes(blockPath, buffer);
         }
 
-        /*private Task WriteAsync(string filePath, byte[] buffer, int offset, int count)
-        {
-            var stream = FileAsync.OpenWrite(filePath);
-            try
-            {
-                stream.Seek(offset, SeekOrigin.Begin);
-                var write = stream.WriteAsync(buffer, 0, count);
-                return write.ContinueWith(s => stream.Close());
-            }
-            catch (Exception ex)
-            {
-                stream.Close();
-                return Task.Factory.FromException(ex);
-            }
-        }*/
+       
 
     }
 }
index f9af7e6..214ea90 100644 (file)
@@ -24,37 +24,7 @@ namespace Pithos.Core.Agents
             }
         }
 
-        public static Task<int> ReadAsync(this FileInfo fileInfo, byte[] buffer, int offset, int count)
-        {
-            //The using statement is not used because we will work asyncronously
-            var stream = FileAsync.OpenRead(fileInfo.FullName);
-            try
-            {
-                stream.Seek(offset, SeekOrigin.Begin);
-                var read = stream.ReadAsync(buffer, offset, count);
-                return read.ContinueWith(t =>
-                {
-                    //Make sure the stream closes
-                    stream.Close();
-                    stream = null;
-                    
-                    t.PropagateExceptions();
-
-                    return t.Result;
-                });
-            }
-            catch (Exception)
-            {
-                //In case of error make sure we close the stream                
-                //The stream may have been 
-                if (stream!=null)
-                    stream.Close();
-                throw;
-            }
-
-        }
-
-        public static string CalculateHash(this FileInfo info,int blockSize,string algorithm)
+       public static string CalculateHash(this FileInfo info,int blockSize,string algorithm)
         {
             if (info==null)
                 throw new ArgumentNullException("info");
index a6ebcb7..32e1c47 100644 (file)
@@ -1,14 +1,11 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.ComponentModel.Composition;
 using System.Diagnostics;
 using System.Diagnostics.Contracts;
 using System.IO;
 using System.Linq;
 using System.Net;
-using System.Text;
-using System.Threading;
 using System.Threading.Tasks;
 using Pithos.Interfaces;
 using Pithos.Network;
@@ -26,33 +23,13 @@ namespace Pithos.Core.Agents
         public IStatusKeeper StatusKeeper { get; set; }
         
         public IStatusNotification StatusNotification { get; set; }
-/*
-        [Import]
-        public FileAgent FileAgent {get;set;}
-*/
-
-       /* public int BlockSize { get; set; }
-        public string BlockHash { get; set; }*/
 
         private static readonly ILog Log = LogManager.GetLogger("NetworkAgent");
 
-        private List<AccountInfo> _accounts=new List<AccountInfo>();
+        private readonly List<AccountInfo> _accounts=new List<AccountInfo>();
 
-        public void Start(/*int blockSize, string blockHash*/)
+        public void Start()
         {
-/*
-            if (blockSize<0)
-                throw new ArgumentOutOfRangeException("blockSize");
-            if (String.IsNullOrWhiteSpace(blockHash))
-                throw new ArgumentOutOfRangeException("blockHash");
-            Contract.EndContractBlock();
-*/
-
-/*
-            BlockSize = blockSize;
-            BlockHash = blockHash;
-*/
-
 
             _agent = Agent<CloudAction>.Start(inbox =>
             {
@@ -64,7 +41,7 @@ namespace Pithos.Core.Agents
                     inbox.LoopAsync(process, loop);
                 };
                 loop();
-            });
+            });           
         }
 
         private async Task Process(CloudAction action)
@@ -81,7 +58,6 @@ namespace Pithos.Core.Agents
             {                
                 Log.InfoFormat("[ACTION] Start Processing {0}", action);
 
-                var localFile = action.LocalFile;
                 var cloudFile = action.CloudFile;
                 var downloadPath = action.GetDownloadPath();
 
@@ -121,16 +97,23 @@ namespace Pithos.Core.Agents
                 }
                 catch (WebException exc)
                 {
-                    Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);                    
+                    Log.ErrorFormat("[WEB ERROR] {0} : {1} -> {2} due to exception\r\n{3}", action.Action, action.LocalFile, action.CloudFile, exc);
                 }
                 catch (OperationCanceledException)
                 {
                     throw;
                 }
-                catch (FileNotFoundException exc)
+                catch (DirectoryNotFoundException)
+                {
+                    Log.ErrorFormat("{0} : {1} -> {2}  failed because the directory was not found.\n Rescheduling a delete",
+                        action.Action, action.LocalFile, action.CloudFile);
+                    //Post a delete action for the missing file
+                    Post(new CloudDeleteAction(action));                    
+                }
+                catch (FileNotFoundException)
                 {
                     Log.ErrorFormat("{0} : {1} -> {2}  failed because the file was not found.\n Rescheduling a delete",
-                        action.Action, action.LocalFile, action.CloudFile, exc);
+                        action.Action, action.LocalFile, action.CloudFile);
                     //Post a delete action for the missing file
                     Post(new CloudDeleteAction(action));
                 }
@@ -241,13 +224,18 @@ namespace Pithos.Core.Agents
             if (cloudAction.LocalFile != null)
             {
                 var accountInfo = cloudAction.AccountInfo;
-                if (cloudAction.LocalFile.Length>accountInfo.BlockSize)
-                    cloudAction.TopHash = new Lazy<string>(() => Signature.CalculateTreeHashAsync(cloudAction.LocalFile,
-                                    accountInfo.BlockSize, accountInfo.BlockHash).Result
-                                     .TopHash.ToHashString());
-                else
+                if (!Directory.Exists(cloudAction.LocalFile.FullName))
                 {
-                    cloudAction.TopHash=new Lazy<string>(()=> cloudAction.LocalHash.Value);
+                    if (cloudAction.LocalFile.Length > accountInfo.BlockSize)
+                        cloudAction.TopHash =
+                            new Lazy<string>(() => Signature.CalculateTreeHashAsync(cloudAction.LocalFile,
+                                                                                    accountInfo.BlockSize,
+                                                                                    accountInfo.BlockHash).Result
+                                                       .TopHash.ToHashString());
+                    else
+                    {
+                        cloudAction.TopHash = new Lazy<string>(() => cloudAction.LocalHash.Value);
+                    }
                 }
 
             }
@@ -270,38 +258,38 @@ namespace Pithos.Core.Agents
         
 
         //Remote files are polled periodically. Any changes are processed
-        public Task ProcessRemoteFiles(DateTime? since=null)
-        {
-            return Task<Task>.Factory.StartNewDelayed(10000, () =>
+        public async Task ProcessRemoteFiles(DateTime? since = null)
+        {            
+            await TaskEx.Delay(TimeSpan.FromSeconds(10),_agent.CancellationToken);
+
+            using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push("All accounts"))
             {
-                using (log4net.ThreadContext.Stacks["Retrieve Remote"].Push("All accounts"))
+
+                try
                 {
                     //Next time we will check for all changes since the current check minus 1 second
                     //This is done to ensure there are no discrepancies due to clock differences
                     DateTime nextSince = DateTime.Now.AddSeconds(-1);
-                    
-                    var tasks=from accountInfo in _accounts
-                              select ProcessAccountFiles(accountInfo, since);
-                    var process=Task.Factory.Iterate(tasks);
 
-                    return process.ContinueWith(t =>
-                    {
-                        if (t.IsFaulted)
-                        {
-                            Log.Error("Error while processing accounts");
-                            t.Exception.Handle(exc=>
-                                                   {
-                                                       Log.Error("Details:", exc);
-                                                       return true;
-                                                   });                            
-                        }
-                        ProcessRemoteFiles(nextSince);
-                    });
+                    var tasks = from accountInfo in _accounts
+                                select ProcessAccountFiles(accountInfo, since);
+
+                    await TaskEx.WhenAll(tasks);
+
+                    ProcessRemoteFiles(nextSince);
                 }
-            });            
+                catch (Exception ex)
+                {
+                    Log.ErrorFormat("Error while processing accounts\r\n{0}",ex);
+                    //In case of failure retry with the same parameter
+                    ProcessRemoteFiles(since);
+                }
+                
+
+            }
         }
 
-        public Task ProcessAccountFiles(AccountInfo accountInfo,DateTime? since=null)
+        public async Task ProcessAccountFiles(AccountInfo accountInfo,DateTime? since=null)
         {   
             if (accountInfo==null)
                 throw new ArgumentNullException("accountInfo");
@@ -318,70 +306,52 @@ namespace Pithos.Core.Agents
                 
                 CreateContainerFolders(accountInfo, containers);
 
-
-                //Get the list of server objects changed since the last check
-                var listObjects = from container in containers
-                                  select Task<IList<ObjectInfo>>.Factory.StartNew(_ =>
-                                        client.ListObjects(accountInfo.UserName, container.Name, since),container.Name);
-
-                var listAll = Task.Factory.WhenAll(listObjects.ToArray());
-                
-                
-
-                //Get the list of deleted objects since the last check
-/*
-                var listTrash = Task<IList<ObjectInfo>>.Factory.StartNew(() =>
-                                client.ListObjects(accountInfo.UserName, FolderConstants.TrashContainer, since));
-
-                var listShared = Task<IList<ObjectInfo>>.Factory.StartNew(() =>
-                                client.ListSharedObjects(since));
-
-                var listAll = Task.Factory.TrackedSequence(
-                    () => listObjects,
-                    () => listTrash,
-                    () => listShared);
-*/
+                try
+                {
+                    
+                    //Get the list of server objects changed since the last check
+                    //The name of the container is passed as state in order to create a dictionary of tasks in a subsequent step
+                    var listObjects = from container in containers
+                                      select  Task<IList<ObjectInfo>>.Factory.StartNew(_ =>
+                                            client.ListObjects(accountInfo.UserName,container.Name, since),container.Name);
 
 
+                    var listTasks = await Task.Factory.WhenAll(listObjects.ToArray());
 
-                var enqueueFiles = listAll.ContinueWith(task =>
-                {
-                    if (task.IsFaulted)
-                    {
-                        //ListObjects failed at this point, need to reschedule
-                        Log.ErrorFormat("[FAIL] ListObjects for{0} in ProcessRemoteFiles with {1}", accountInfo.UserName,task.Exception);
-                        return;
-                    }
                     using (log4net.ThreadContext.Stacks["SCHEDULE"].Push("Process Results"))
                     {
-                        var dict=task.Result.ToDictionary(t=> t.AsyncState);
-                        
+                        var dict = listTasks.ToDictionary(t => t.AsyncState);
+
                         //Get all non-trash objects. Remember, the container name is stored in AsyncState
-                        var remoteObjects = from objectList in task.Result
-                                            where (string)objectList.AsyncState != "trash"
+                        var remoteObjects = from objectList in listTasks
+                                            where (string) objectList.AsyncState != "trash"
                                             from obj in objectList.Result
                                             select obj;
-                                                                       
+
                         var trashObjects = dict["trash"].Result;
                         //var sharedObjects = ((Task<IList<ObjectInfo>>) task.Result[2]).Result;
 
                         //Items with the same name, hash may be both in the container and the trash
                         //Don't delete items that exist in the container
                         var realTrash = from trash in trashObjects
-                                        where !remoteObjects.Any(info => info.Name == trash.Name &&  info.Hash == trash.Hash)
+                                        where
+                                            !remoteObjects.Any(
+                                                info => info.Name == trash.Name && info.Hash == trash.Hash)
                                         select trash;
-                        ProcessDeletedFiles(accountInfo,realTrash);                        
+                        ProcessDeletedFiles(accountInfo, realTrash);
 
 
-                        var remote = from info in remoteObjects//.Union(sharedObjects)
+                        var remote = from info in remoteObjects
+                                     //.Union(sharedObjects)
                                      let name = info.Name
                                      where !name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase) &&
-                                           !name.StartsWith(FolderConstants.CacheFolder +"/", StringComparison.InvariantCultureIgnoreCase)
+                                           !name.StartsWith(FolderConstants.CacheFolder + "/",
+                                                            StringComparison.InvariantCultureIgnoreCase)
                                      select info;
 
                         //Create a list of actions from the remote files
-                        var allActions = ObjectsToActions(accountInfo,remote);
-                       
+                        var allActions = ObjectsToActions(accountInfo, remote);
+
                         //And remove those that are already being processed by the agent
                         var distinctActions = allActions
                             .Except(_agent.GetEnumerable(), new PithosMonitor.LocalFileComparer())
@@ -393,30 +363,17 @@ namespace Pithos.Core.Agents
                             Post(message);
                         }
 
-                        /*                        //Report the number of new files
-                                                var remoteCount = distinctActions.Count(action=>
-                                                    action.Action==CloudActionType.DownloadUnconditional);
-
-                                                if ( remoteCount > 0)
-                                                    StatusNotification.NotifyChange(String.Format("Processing {0} new files", remoteCount));
-                        */
-
-                        Log.Info("[LISTENER] End Processing");                        
+                        Log.Info("[LISTENER] End Processing");
                     }
-                });
+                }
+                catch (Exception ex)
+                {
+                    Log.ErrorFormat("[FAIL] ListObjects for{0} in ProcessRemoteFiles with {1}", accountInfo.UserName, ex);
+                    return;
+                }
+
+                Log.Info("[LISTENER] Finished");
 
-                var log = enqueueFiles.ContinueWith(t =>
-                {                    
-                    if (t.IsFaulted)
-                    {
-                        Log.Error("[LISTENER] Exception", t.Exception);
-                    }
-                    else
-                    {
-                        Log.Info("[LISTENER] Finished");
-                    }
-                });
-                return log;
             }
         }
 
@@ -510,7 +467,7 @@ namespace Pithos.Core.Agents
             
             var newFilePath = action.LocalFile.FullName;            
             //The local file is already renamed
-            this.StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Modified);
+            StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Modified);
 
 
             var account = action.CloudFile.Account ?? accountInfo.UserName;
@@ -519,8 +476,8 @@ namespace Pithos.Core.Agents
             var client = new CloudFilesClient(accountInfo);
             client.MoveObject(account, container, action.OldCloudFile.Name, container, action.CloudFile.Name);
 
-            this.StatusKeeper.SetFileStatus(newFilePath, FileStatus.Unchanged);
-            this.StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Normal);
+            StatusKeeper.SetFileStatus(newFilePath, FileStatus.Unchanged);
+            StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Normal);
             NativeMethods.RaiseChangeNotification(newFilePath);
         }
 
@@ -543,7 +500,7 @@ namespace Pithos.Core.Agents
                 var info = fileAgent.GetFileInfo(fileName);                
                 var fullPath = info.FullName.ToLower();
 
-                this.StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
+                StatusKeeper.SetFileOverlayStatus(fullPath, FileOverlayStatus.Modified);
 
                 var account = cloudFile.Account ?? accountInfo.UserName;
                 var container = cloudFile.Container ;//?? FolderConstants.PithosContainer;
@@ -551,7 +508,7 @@ namespace Pithos.Core.Agents
                 var client = new CloudFilesClient(accountInfo);
                 client.DeleteObject(account, container, cloudFile.Name);
 
-                this.StatusKeeper.ClearFileStatus(fullPath);
+                StatusKeeper.ClearFileStatus(fullPath);
             }
         }
 
@@ -572,7 +529,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The localPath must be rooted", "localPath");
             Contract.EndContractBlock();
                        
-            Uri relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
+            var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
 
             var url = relativeUrl.ToString();
             if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
@@ -590,22 +547,31 @@ namespace Pithos.Core.Agents
                 var account = cloudFile.Account;
                 var container = cloudFile.Container;
 
-                //Retrieve the hashmap from the server
-                var serverHash = await client.GetHashMap(account, container, url);
-                //If it's a small file
-                if (serverHash.Hashes.Count == 1 )
-                    //Download it in one go
-                    await DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
-                    //Otherwise download it block by block
+                if (cloudFile.Content_Type == @"application/directory")
+                {
+                    if (!Directory.Exists(localPath))
+                        Directory.CreateDirectory(localPath);
+                }
                 else
-                    await DownloadWithBlocks(accountInfo,client, cloudFile, relativeUrl, localPath, serverHash);                
-
-                if (cloudFile.AllowedTo == "read")
                 {
-                    var attributes=File.GetAttributes(localPath);
-                    File.SetAttributes(localPath,attributes|FileAttributes.ReadOnly);
+                    //Retrieve the hashmap from the server
+                    var serverHash = await client.GetHashMap(account, container, url);
+                    //If it's a small file
+                    if (serverHash.Hashes.Count == 1)
+                        //Download it in one go
+                        await
+                            DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
+                        //Otherwise download it block by block
+                    else
+                        await DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
+
+                    if (cloudFile.AllowedTo == "read")
+                    {
+                        var attributes = File.GetAttributes(localPath);
+                        File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);                        
+                    }
                 }
-                
+
                 //Now we can store the object's metadata without worrying about ghost status entries
                 StatusKeeper.StoreInfo(localPath, cloudFile);
                 
@@ -751,10 +717,6 @@ namespace Pithos.Core.Agents
 
             try
             {
-                if (action == null)
-                    throw new ArgumentNullException("action");
-                Contract.EndContractBlock();
-
                 var accountInfo = action.AccountInfo;
 
                 var fileInfo = action.LocalFile;
@@ -806,7 +768,7 @@ namespace Pithos.Core.Agents
                     if (hash == cloudHash || topHash == cloudHash)
                     {
                         //but store any metadata changes 
-                        this.StatusKeeper.StoreInfo(fullFileName, info);
+                        StatusKeeper.StoreInfo(fullFileName, info);
                         Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
                         return;
                     }
@@ -828,7 +790,7 @@ namespace Pithos.Core.Agents
                     await UploadWithHashMap(accountInfo, cloudFile, fileInfo, cloudFile.Name, treeHash);
 
                     //If everything succeeds, change the file and overlay status to normal
-                    this.StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal);
+                    StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal);
                 }
                 //Notify the Shell to update the overlays
                 NativeMethods.RaiseChangeNotification(fullFileName);
index 947b1f7..4d3df6e 100644 (file)
@@ -163,77 +163,6 @@ namespace Pithos.Network
         }
 
         
-        //The Task Iterator style allows the execution of a sequence of patterns using
-        //iterator syntax.
-        //This particular variation returns the result of the last task, if there is one
-        public static Task<TResult> Iterate<TResult>(IEnumerable<Task> asyncIterator)
-        {
-            if (asyncIterator == null) 
-                throw new ArgumentNullException("asyncIterator");
-            var enumerator = asyncIterator.GetEnumerator();
-            if (enumerator == null)
-                throw new InvalidOperationException("Invalid enumerable - GetEnumerator returned null");
-            
-            var tcs = new TaskCompletionSource<TResult>();
-            tcs.Task.ContinueWith(_ => enumerator.Dispose(), TaskContinuationOptions.ExecuteSynchronously);
-            
-            Action<Task> recursiveBody = null;
-            recursiveBody = delegate
-            {
-                try
-                {
-                    if (enumerator.MoveNext())
-                        enumerator.Current.ContinueWith(recursiveBody,
-                                                        TaskContinuationOptions.ExecuteSynchronously);
-                    else
-                    {
-                        var lastTask = enumerator.Current as Task<TResult>;
-                        var result = (lastTask !=null ) 
-                            ? lastTask.Result
-                            : default(TResult);
-
-                        tcs.TrySetResult(result);
-                    }
-                }
-                catch (Exception exc)
-                {
-                    tcs.TrySetException(exc);
-                }
-            };
-            recursiveBody(null);
-            return tcs.Task;
-        }
-
-
-        /*  public static byte[] CalculateTopHash(IEnumerable<byte[]> hashMap, string algorithm)
-        {
-            if (hashMap == null)
-                throw new ArgumentNullException("hashMap");
-            if (String.IsNullOrWhiteSpace(algorithm))
-                throw new ArgumentNullException("algorithm");
-            Contract.EndContractBlock();
-
-            var hashCount = hashMap.Count();
-            if (hashCount == 0)
-                return null;
-            using (var hasher = HashAlgorithm.Create(algorithm))
-            {
-                var i = 0;
-                var count = hashCount;
-                foreach (var block in hashMap)
-                {
-                    if (i++ != count - 1)
-                        hasher.TransformBlock(block, 0, block.Length, null, 0);
-                    else
-                        hasher.TransformFinalBlock(block, 0, block.Length);
-                }
-
-                var finalHash = hasher.Hash;
-
-                return finalHash;
-            }
-        }*/
-
         public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
         {
             if (hashMap == null)
index 4b9f967..c796961 100644 (file)
@@ -132,7 +132,7 @@ namespace Pithos.Network
         }
 
         //Saves the Json representation to a file
-        public Task Save(string filePath)
+        public async Task Save(string filePath)
         {
             if (String.IsNullOrWhiteSpace(filePath))
                 throw new ArgumentNullException("filePath");
@@ -142,24 +142,20 @@ namespace Pithos.Network
             var path = Path.Combine(filePath, fileName);
             if (!Directory.Exists(filePath))
                 Directory.CreateDirectory(filePath);
-
-            return Task.Factory
-                .StartNew<string>(ToJson)
-                .ContinueWith(jsonTask=> 
-                FileAsync.WriteAllText(path, jsonTask.Result)).Unwrap();
+            
+            var json=await TaskEx.Run(()=>ToJson());
+            await FileAsync.WriteAllText(path, json);
         }
 
-        public static Task<TreeHash> LoadTreeHash(string dataPath,Guid fileId)
+        public static async Task<TreeHash> LoadTreeHash(string dataPath,Guid fileId)
         {
             var fileName = fileId.ToString("N");
             var path = Path.Combine(dataPath, fileName);
-            return FileAsync.ReadAllText(path).ContinueWith(loadTask =>
-            {
-                var json = loadTask.Result;
-                var treeHash = Parse(json);
-                treeHash.FileId = fileId;
-                return treeHash;
-            });
+            
+            var json=await FileAsync.ReadAllText(path);
+            var treeHash = Parse(json);
+            treeHash.FileId = fileId;
+            return treeHash;
         }
 
         public static TreeHash Empty = new TreeHash(DEFAULT_HASH_ALGORITHM)