Question

From what I've seen about using the Async CTP with the event asynchronous pattern, the code I have here should work fine, with var result1 = await tcs1.Task blocking until clientGetFileList.GetCompleted fires. However, what ends up happening is that I get bounced back to GetRestoreStream, on return GetRestoreStreamAwait().Result which never does return -- instead, my app pretty much locks up on me.

Can someone please explain to me what it is I am doing wrong?

protected override Stream GetRestoreStream()
{
    if (SkyDriveFolderId != null)
        return GetRestoreStreamAwait().Result;

    return Stream.Null;
}

private async Task<Stream> GetRestoreStreamAwait()
{
    LiveConnectClient clientGetFileList = new LiveConnectClient(_session);
    TaskCompletionSource<LiveOperationCompletedEventArgs> tcs1 = new TaskCompletionSource<LiveOperationCompletedEventArgs>();
    EventHandler<LiveOperationCompletedEventArgs> d1 = (o, e) => { tcs1.TrySetResult(e); };

    clientGetFileList.GetCompleted += d1;
    clientGetFileList.GetAsync(SkyDriveFolderId + "/files");
    var result1 = await tcs1.Task;
    clientGetFileList.GetCompleted -= d1;

    // ... method continues for a while
}

Update: This bit of code seems to move through all the way, but task.Start() tosses off an InvalidOperationException so I never actually get the stream at the end. Wrapping it in a try/catch doesn't change anything, either -- without the try/catch the InvalidOperationException is caught further up the stack while the operation runs happily ignorant of the fact its result will never be used; with it, task.Result freezes things just as surely as the code above.

protected override Stream GetRestoreStream()
{
    if (SkyDriveFolderId != null)
    {
        var task = GetRestoreStreamImpl();
        task.Start();
        return task.Result;
    }

    return Stream.Null;
}

private async Task<Stream> GetRestoreStreamImpl()
{
    var getResult = await GetTaskAsync(SkyDriveFolderId + "/files");

    List<object> data = (List<object>)getResult["data"];
    foreach (IDictionary<string, object> dictionary in data)
    {
        if (dictionary.ContainsKey("name") && (string)dictionary["name"] == BackupFileName)
        {
            if (dictionary.ContainsKey("id"))
            {
                SkyDriveFileId = (string)dictionary["id"];
                break;
            }
        }
    }

    if (String.IsNullOrEmpty(SkyDriveFileId))
    {
        MessageBox.Show("Restore failed: could not find backup file", "Backup", MessageBoxButton.OK);
        return Stream.Null;
    }

    return await DownloadTaskAsync(SkyDriveFileId + "/content");
}

private Task<IDictionary<string,object>> GetTaskAsync(string path)
{
    var client = new LiveConnectClient(_session);
    var tcs = new TaskCompletionSource<IDictionary<string, object>>();

    client.GetCompleted += (o, e) =>
        {
            if (e.Error != null)
                tcs.TrySetException(e.Error);
            else if (e.Cancelled)
                tcs.TrySetCanceled();
            else
                tcs.TrySetResult(e.Result);
        };
    client.GetAsync(path);
    return tcs.Task;
}

private Task<Stream> DownloadTaskAsync(string path)
{
    var client = new LiveConnectClient(_session);
    var tcs = new TaskCompletionSource<Stream>();

    client.DownloadCompleted += (o, e) =>
        {
            if (e.Error != null)
                tcs.TrySetException(e.Error);
            else if (e.Cancelled)
                tcs.TrySetCanceled();
            else
                tcs.TrySetResult(e.Result);
        };
    client.DownloadAsync(path);
    return tcs.Task;
}
Was it helpful?

Solution

You are misunderstanding the way that async/await works. Basically, your code is blocking at the var result1 and below. However, what await allows is for the code that called the async method (GetRestoreStream in this case) to be returned to as soon as a long running task* with await in front of it is called. If you were not reliant on the .Result, then your GetRestoreStream method would complete. However, since you require the result, then your GetRestoreStream method becomes synchronous while it waits for GetRestoreStreamAwait to complete. I will add some visuals shortly.

Here is some sample code flow:

-GetRestoreStream calls GetRestoreStreamAwait
---GetRestoreStreamAwait calls an async task
-GetRestoreStreamAwait returns to GetRestoreStream with a pending result
-GetRestoreStream can do anything it wants, but if it calls for the pending result, it will block
---GetRestoreStreamAwait finally finishes its async task and continues through its code, returning a result
-Any code in GetRestoreStream that was waiting for the result receives the Result

This is not the best graphical representation, hopefully it helps explain it a little though. The thing to notice is that the code flow is not what you are used to due to the nature of async

So, my guess is that your app locks up only because you are trying to access the Result that is not available yet, and all you should have to do is wait for the tcs1.Task to complete. If you want to avoid locking up, then you will need to nest the call so that GetRestoreStream is also an async method. However, if the result is what you are ultimately looking for, then you will need to wait for the return, or simply set up a callback like you normally would for the async pattern

*Notice that I said long running task because the compiler will not waste time rewriting code that is already completed (if it is indeed completed by the time the await is called)

UPDATE...try this

protected override Stream GetRestoreStream()
{
    if (SkyDriveFolderId != null)
        return GetRestoreStreamAwait().Result;

    return Stream.Null;
}

private async Task<Stream> GetRestoreStreamAwait()
{

    try
    {
    LiveConnectClient clientGetFileList = new LiveConnectClient(_session);
    TaskCompletionSource<LiveOperationCompletedEventArgs> tcs1 = new TaskCompletionSource<LiveOperationCompletedEventArgs>();
    EventHandler<LiveOperationCompletedEventArgs> d1 = 
        (o, e) => 
            { 
                try
                {
                    tcs1.TrySetResult(e); 
                }
                catch(Exception ex)
                {
                    tcs1.TrySetResult(null);
                }
            };

    clientGetFileList.GetCompleted += d1;
    clientGetFileList.GetAsync(SkyDriveFolderId + "/files");
    var result1 = await tcs1.Task;
    clientGetFileList.GetCompleted -= d1;

    // ... method continues for a while
   }
   catch(Exception ex)
   {
       return null;
   }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top