4

イベント非同期パターンで非同期CTPを使用することについて私が見たものから、ここにあるコードは、起動var result1 = await tcs1.Taskするまでブロックして、正常に機能するはずclientGetFileList.GetCompletedです。ただし、最終的に発生するのは、GetRestoreStreamにバウンスされて、GetRestoreStreamにreturn GetRestoreStreamAwait().Result戻らないことです。代わりに、アプリがほとんどロックされます。

誰かが私が間違っていることを私に説明してもらえますか?

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

1 に答える 1

3

あなたはその仕組みを誤解していasync/awaitます。基本的に、コードはvarresult1以下でブロックされています。ただし、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も非同期メソッドになるように、呼び出しをネストする必要があります。ただし、結果が最終的に探しているものである場合は、返されるのを待つか、非同期パターンの場合と同じようにコールバックを設定する必要があります。

*コンパイラーはすでに完了しているコードの書き換えに時間を浪費しないため、長時間実行タスクを言ったことに注意してください(待機が呼び出されるまでに実際に完了した場合)

更新...これを試してください

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-04T02:31:40.393 に答える