【问题标题】:Using async/await with Live SDK在 Live SDK 中使用 async/await
【发布时间】:2012-04-04 12:31:24
【问题描述】:

从我所看到的关于将 Async 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;
}

【问题讨论】:

标签: c# asynchronous windows-phone-7.1 live-connect-sdk


【解决方案1】:

您误解了async/await 的工作方式。基本上,您的代码在 var result1 及以下位置阻塞。然而,await 允许调用异步方法(在这种情况下为GetRestoreStream)的代码在调用前面有await 的长时间运行的任务* 时立即返回。如果您不依赖 .Result,那么您的 GetRestoreStream 方法将完成。但是,由于您需要结果,因此您的 GetRestoreStream 方法在等待 GetRestoreStreamAwait 完成时将变为同步。我会尽快添加一些视觉效果。

这里是一些示例代码流:

-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;
   }
}

【讨论】:

  • 从我所读到的,Task 对象应该被“热”返回,如果Start 被多次调用,则抛出异常。此外,由于父类对我正在处理的方法的要求,我正在尝试将一系列 EAP 异步方法变为同步方法。
  • 问题是,不再让所有东西都坐在那里多久,第一个异步任务似乎永远不会完成。我从来没有达到result1 被填满而d1 被解开的地步。我认为下一个事件也是如此,但无论我让模拟器和调试器运行多长时间,我都没有达到这一点。
  • @ChrisCharabaruk 我对 TrySetResult 不太熟悉。我知道它可以像你一样使用,但我不能确定。您可以尝试将代码包装在常规 Get 的任务中,然后查看它是否返回。我的猜测是,这个异步调用只是没有正确配置,并且一直在旋转直到它得到一个返回......它永远不会。也许常规的 get 会抛出错误?
  • 没有常规的Get。所有LiveConnectClient 调用都是使用事件模式异步的。
  • @ChrisCharabaruk 尝试将您的通话包装在 Try-Catch 中。如果您不观察它们,异步将吞噬您的异常:blogs.msdn.com/b/ericlippert/archive/2010/11/23/…
猜你喜欢
  • 2018-09-10
  • 2021-06-05
  • 2021-05-28
  • 2020-10-12
  • 1970-01-01
  • 1970-01-01
  • 2017-01-23
  • 2019-07-16
  • 1970-01-01
相关资源
最近更新 更多