Using async/await with Live SDK

Go To StackoverFlow.com

4

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;
}
2012-04-04 02:24
by Chris Charabaruk
Seems that Microsoft already has an async/await example for the Live Connect SDK and that my Google-fu needs some work. The sample is at https://github.com/liveservices/LiveSDK/tree/master/Samples/WindowsPhone/LiveSDKAsyncAwaitSample and does things somewhat differently to what I was trying - Chris Charabaruk 2012-04-05 01:30


3

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;
   }
}
2012-04-04 02:31
by Justin Pihony
From what I've read, Task objects are supposed to be returned "hot" and throw an exception if Start is called on them more than once. Also, what I'm trying to do is make a chain of EAP async methods into a synchronous one, due to the requirement of the parent class to the one I'm working on - Chris Charabaruk 2012-04-04 02:55
Thing is, no longer how long I let everything sit there, that first async task never seems to finish. I never get to the point where result1 gets filled and d1 gets unhooked. I figure it'd be the same case with the next event, too, but I've never gotten to that point no matter how long I've let the emulator and debugger run - Chris Charabaruk 2012-04-04 02:57
@ChrisCharabaruk I am not as familiar with the TrySetResult. I know that it can be used kind of like you are doing, however I cannot be sure. Can you try to just wrap the code in a task for a regular Get and see if it returns. My guess is that this async call is just not configured right, and spinning until it gets a return....which it never will. Maybe a regular get will throw an error - Justin Pihony 2012-04-04 03:03
There is no regular Get. All LiveConnectClient calls are async using the event pattern - Chris Charabaruk 2012-04-04 03:07
@ChrisCharabaruk Try wrapping your calls in a Try-Catch. Async will swallow exceptions on you if you don't observe them: http://blogs.msdn.com/b/ericlippert/archive/2010/11/23/asynchrony-in-c-5-part-eight-more-exceptions.asp - cunningdave 2012-04-04 03:10
@ChrisCharabaruk OK, what happens if you call this using the normal async event setup? (no awaits and tasks)? Basically, can you verify that the call WILL return - Justin Pihony 2012-04-04 03:11
@cunningdave I just skimmed that article, but will have to read it more in depth soon. I swear that the last time I played with async in depth it was not swallowing the errors, but it has been a couple months, so my brain might be rusty : - Justin Pihony 2012-04-04 03:13
@JustinPihony I'd try that, but I have no way to toss the final result back to GetRestoreStream. That's why I tried switching to async/await in the first place - Chris Charabaruk 2012-04-04 03:36
I think cunningdave is correct, wrap your async method in a try/catch to make sure nothing is being eaten. I believe that a .Result should have observe the exception, though.. - Justin Pihony 2012-04-04 03:43
I tried the event async way and it's going all the way through. Only that I can't get at the final result. Trying an event in the final callback and using TaskCompletionSource to wait on for it, but I end up getting an InvalidOperationException out of Task.RunSynchronously (TaskRunSynchronouslyPromise), Task.Start (TaskStartNullAction).. - Chris Charabaruk 2012-04-04 03:54
@ChrisCharabaruk OK, I have added some code at the bottom of my answer, mainly dealing with potential errors. Try and see if it still hangs - Justin Pihony 2012-04-04 04:06
Nothing seems to be getting thrown, and it continues hanging. I'm going to update with some code that seems to be mostly there, except for Task tossing InvalidOperationException at me - Chris Charabaruk 2012-04-04 04:12
Ads