使用async/await与Live SDK

4
根据我对使用异步CTP与事件异步模式的了解,这里的代码应该可以正常工作,var result1 = await tcs1.Task会阻塞直到clientGetFileList.GetCompleted触发。然而,实际情况是当我执行return GetRestoreStreamAwait().Result时,我被弹回到了GetRestoreStream,并且它永远不会返回——相反,我的应用程序几乎完全卡住了。
请问有人能够解释一下我做错了什么吗?
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
}

更新: 这段代码似乎一直在执行,但是 task.Start() 抛出了一个 InvalidOperationException ,所以我实际上从未获得最终的流。使用 try/catch 包装它也不会改变任何东西 - 没有 try/catch 时,InvalidOperationException 在操作运行时被更高级别的堆栈捕获,而不知道其结果永远不会被使用;使用它时,task.Result 就像上面的代码一样冻结了事情。
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;
}

似乎微软已经为Live Connect SDK提供了一个async/await示例,而我的Google技能还需要加强。该示例位于https://github.com/liveservices/LiveSDK/tree/master/Samples/WindowsPhone/LiveSDKAsyncAwaitSample,并且与我尝试的方法有些不同。 - Chris Charabaruk
1个回答

3
你误解了async/await的工作方式。基本上,你的代码在变量result1及其以下行处阻塞。然而,await所允许的是,在调用异步方法(在此情况下是GetRestoreStream)的代码被返回时,只要一个带有await的长时间运行任务*被调用就可以了。如果你不依赖于.Result,那么你的GetRestoreStream方法将会完成。但是,由于你需要结果,所以在等待GetRestoreStreamAwait完成时,你的GetRestoreStream方法变成同步方法。我很快会添加一些视觉效果。
以下是一些示例代码流程:
-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

这不是最好的图形表示,但希望能帮助你理解。需要注意的是,由于异步的特性,代码流程并不是你想象中的那样。
所以,我的猜测是你的应用程序仅因为正在尝试访问尚不可用的“Result”,才会锁定,而且你只需要等待“tcs1.Task”完成即可。如果你想避免锁定,则需要嵌套调用,使GetRestoreStream也成为异步方法。然而,如果结果是你最终要寻找的东西,那么你需要等待返回,或者像通常为异步模式设置回调一样。
需要注意的是,我说了长时间运行的任务,因为编译器不会浪费时间重写已经完成的代码(如果await被调用时确实已经完成)。
更新...请尝试这个
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;
   }
}

问题是,无论我让所有东西停留多长时间,第一个异步任务似乎永远不会完成。我从来没有达到过result1被填充和d1被取消挂钩的点。我想下一个事件也会是同样的情况,但无论我让模拟器和调试器运行多长时间,我都没有达到那个点。 - Chris Charabaruk
没有常规的Get方法。所有LiveConnectClient调用都使用事件模式异步进行。 - Chris Charabaruk
@ChrisCharabaruk 试着将你的调用包装在 Try-Catch 中。 如果您不观察它们,异步将会吞噬异常:http://blogs.msdn.com/b/ericlippert/archive/2010/11/23/asynchrony-in-c-5-part-eight-more-exceptions.aspx - cunningdave
我尝试了事件异步方式,它一直在进行中。只是我无法得到最终结果。尝试在最终回调中使用事件,并使用TaskCompletionSource等待它,但最终出现了InvalidOperationException,其中包括Task.RunSynchronously(Task_RunSynchronously_Promise)、Task.Start(Task_Start_NullAction)... - Chris Charabaruk
似乎没有任何异常被抛出,程序继续挂起。我将更新一些代码,这些代码似乎大部分都在那里,除了Task向我抛出InvalidOperationException。 - Chris Charabaruk
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接