3

私は単純な地下鉄アプリを書いています。ただし、ファイルにアクセスすると API がブロックされます。ブロックするとは、プログラムが永遠に待機することを意味します。ファイルまたはフォルダーの作成/開くには、最大で数秒かかります。この場合、それは永遠にかかります。

プログラムを実行すると、OnTest から戻ってきません。それはあなたが得るものですか。了解しました。ファイルとフォルダの作成が完了するまで待機します。多分それは素晴らしいデザインではありません。しかし、それは問題ではありません。

私の質問は:

  • 同じ動作をしますか (プログラムを永久にブロックします)
  • それは起こるべきことですか、それとも WinRT のバグですか? (私は消費者向けプレビューを使用しています)
  • それが期待される動作である場合、なぜ永遠にかかるのでしょうか?

XAML コードは次のとおりです。

<Button Click="OnTest">Test</Button>

C# コードは次のとおりです。

 private async void OnTest(object sender, RoutedEventArgs e)
        {
            var t = new Cache("test1");
            t = new Cache("test2");
            t = new Cache("test3");
        }
        class Cache
        {
            public Cache(string name)
            {
                TestRetrieve(name).Wait();
            }
            public static async Task TestRetrieve(string name) 
            {
                StorageFolder rootFolder = ApplicationData.Current.LocalFolder;
                var _folder = await rootFolder.CreateFolderAsync(name, CreationCollisionOption.OpenIfExists);
                var file = await _folder.CreateFileAsync("test.xml", CreationCollisionOption.OpenIfExists);
            }
        }

new Cache("test2"); への 2 回目の呼び出しでブロックされます。

4

4 に答える 4

15

私はあなたのプログラムを実行したり、あなたの問題を再現したりしようとはしていませんが、何が起こっているのかについて知識に基づいた推測をすることができます。

次のやることリストを自分で書いたとします。

  • 郵便受けにママに手紙を入れなさい。
  • 彼女の返事を読んだらすぐに目を覚ますようにアラームを設定してください。
  • 寝る。
  • メールボックスで返信を確認してください。
  • 返信を読んでください。

次に、そのリストのすべてを上から下に厳密に実行します。何が起こるのですか?

問題は郵便局やお母さんにはありません。彼らはあなたが郵便受けに入れた手紙を受け取り、それをお母さんに送り、お母さんは彼女の返事を書き、郵便局はそれをあなたに持ち帰ります。問題は、 5番目のステップを完了してからアラームが目覚めた後にのみ4番目のステップを開始できるため、4番目のステップに到達できないことです。あなたは本質的にあなたの将来の自己があなたの現在の自己を目覚めさせるのを待っているので、あなたは永遠に眠ります。

エリック、説明ありがとうございます。

どういたしまして。

ただし、コードが機能しない理由についてはまだ混乱しています。

OK、分解しましょう。あなたのプログラムは実際に何をしていますか?単純化しましょう:

void M()
{
    Task tx = GetATask();
    tx.Wait();
}
async Task GetATask()
{
    Task ty = DoFileSystemThingAsync();
    await ty;
    DoSomethingElse();
}

まず最初に:タスクとは何ですか?タスクは、(1)実行するジョブ、および(2)タスクの継続への委任(タスクの実行に発生する必要があること)を表すオブジェクトです。

したがって、GetATaskを呼び出します。それは何をするためのものか?さて、それが最初に行うことは、タスクを作成し、それをtyに格納することです。そのタスクは、「ディスク上で何らかの操作を開始し、完了したらI/O完了スレッドに通知する」というジョブを表します。

そのタスクの継続は何ですか?そのタスクが完了した後、何が起こらなければなりませんか?DoSomethingElseを呼び出す必要があります。そのため、コンパイラはawaitを一連のコードに変換し、タスクが完了したときにDoSomethingElseが呼び出されるようにタスクに指示します。

I / Oタスクの継続が設定された瞬間に、メソッドGetATaskはタスクを呼び出し元に返します。それは何の仕事ですか?これは、 tyに格納されたタスクとは異なるタスクです。返されるタスクは、GetATaskメソッドが実行する必要があるすべてのことを実行するジョブを表すタスクです

そのタスクの継続は何ですか?わからない!それは、GetATaskの呼び出し元が決定する必要があります。

では、確認しましょう。2つのタスクオブジェクトがあります。1つは、「ファイルシステムでこのことを実行する」というタスクを表します。これは、ファイルシステムが機能するときに実行されます。続きは「DoSomethingを呼び出す」です。「GetATaskの本体ですべてを実行する」というジョブを表す2番目のタスクオブジェクトがあります。これは、DoSomethingElseへの呼び出しが戻った後に実行されます。

繰り返しますが、ファイルI / Oが成功すると、最初のタスクが完了します。その場合、ファイルI / O完了スレッドは、「ねえ、あなたが待っていたファイルI / Oが完了しました。これは、DoSomethingElseを呼び出すときが来たので、これを伝えています。 "。

しかし、メインスレッドはそのメッセージキューを調べていません。なぜだめですか?DoSomethingElseを含むGetATaskのすべてが完了するまで同期的に待機するように指示したためです。ただし、DoSomethingElseが完了するのを待っているため、DoSomethingElseを実行するように指示するメッセージを処理できません。

今それは明らかですか?「DoSomethingElseを呼び出してください」がこのスレッドで実行される作業のキューにあるかどうかを確認する前に、スレッドがDoSomethingElseの実行を完了するまで待機するようにスレッドに指示しています。お母さんからの手紙を読むまで待っていますが、同期して待っているということは、メールボックスをチェックして手紙が届いたかどうかを確認していないことを意味します

この場合、Waitを呼び出すのは明らかに間違っています。なぜなら、将来何かをするのを待っているからです。それはうまくいきません。しかし、より一般的には、Waitを呼び出すと、そもそも非同期であるという全体的なポイントが完全に無効になります。そんなことはしないでください。「非同期にしたい」と「同期的に待ちたい」の両方を言っても意味がありません。それらは反対です。

于 2012-04-11T23:13:06.227 に答える
7

Wait()Cache クラスのコンストラクターで使用しています。これは、現在非同期で実行されているものが完了するまでブロックされます。

これは、これを設計する方法ではありません。コンストラクターと非同期は意味がありません。おそらく、次のようなファクトリ メソッド アプローチの方がうまくいくでしょう。

public class Cache
{
    private string cacheName;

    private Cache(string cacheName)
    {
        this.cacheName = cacheName;
    }

    public static async Cache GetCacheAsync(string cacheName)
    {
         Cache cache = new Cache(cacheName);

         await cache.Initialize();

         return cache;
    }

    private async void Initialize()    
    {   
            StorageFolder rootFolder = ApplicationData.Current.LocalFolder;   
            var _folder = await rootFolder.CreateFolderAsync(this.cacheName, CreationCollisionOption.OpenIfExists);   
            var file = await _folder.CreateFileAsync("test.xml", CreationCollisionOption.OpenIfExists);
   }
}

そして、次のように使用します。

await Task.WhenAll(Cache.GetCacheAsync("cache1"), Cache.GetCacheAsync("cache2"), Cache.GetCacheAsync("cache3"));   
于 2012-04-11T22:11:24.333 に答える
1
TestRetrieve(name).Wait();

呼び出しを使用して、具体的にブロックするように指示してい.Wait()ます。

を削除する.Wait()と、ブロックされなくなります。

于 2012-04-11T22:08:55.950 に答える
0

既存の回答は、ブロックする理由とブロックしないようにする方法のコード例の非常に完全な説明を提供しますが、これらの多くは、一部のユーザーが理解するよりも「より多くの情報」です。これは、より単純な「力学指向」の説明です..

非同期/待機パターンの動作方法では、非同期メソッドを待機するたびに、そのメソッドの非同期コンテキストを現在のメソッドの非同期コンテキストに「アタッチ」しています。await が魔法の隠しパラメーター「コンテキスト」を渡すと想像してみてください。この context-parameter は、ネストされた await 呼び出しを既存の非同期コンテキストにアタッチできるようにするものです。(これは単なる例えです...詳細はこれよりも複雑です)

非同期メソッド内にいて、メソッドを同期的に呼び出す場合、その同期メソッドはその魔法の隠し非同期コンテキストパラメーターを取得しないため、何もアタッチできません。その場合、「待機」を使用してそのメソッド内に新しい非同期コンテキストを作成する操作は無効です。これは、新しいコンテキストがスレッドの既存のトップレベルの非同期コンテキストに関連付けられないためです (それがないためです!)。

コード例で説明すると、TestRetrieve(name).Wait(); あなたが思っていることをしていません。実際には、現在のスレッドに、上部の async-activity-wait-loop に再び入るように指示しています。この例では、これは OnTest ハンドラーと呼ばれる UI スレッドです。次の図が役立つ場合があります。

UIスレッドのコンテキストは次のようになります...

UI-Thread ->
    OnTest

接続された await/async 呼び出しのチェーンがずっと下になかったので、TestRetrieve 非同期コンテキストを上記の UI-Thread 非同期チェーンに「アタッチ」したことはありません。事実上、作成した新しいコンテキストはどこにもぶら下がっているだけです。したがって、UIThread を「待機」すると、すぐにトップに戻ります。

非同期を機能させるには、実行する必要があるすべての非同期アクションを通じて、最上位の同期スレッド (この場合はこれを実行する UI スレッド) から接続された非同期/待機チェーンを維持する必要があります。コンストラクターを非同期にすることはできないため、非同期コンテキストをコンストラクターにチェーンすることはできません。オブジェクトを同期的に構築し、外部からの TestRetrieve を待機する必要があります。コンストラクターから「待機」行を削除し、これを行います...

await (new Cache("test1")).TestRetrieve("test1");

これを行うと、「TestRetrieve」非同期コンテキストが適切にアタッチされるため、チェーンは次のようになります。

UI-Thread ->
    OnTest ->
        TestRetrieve

これで、UI スレッド async-loop-handler は、非同期完了時に TestRetrieve を適切に再開できるようになり、コードは期待どおりに動作します。

「非同期コンストラクター」を作成する場合は、ドリューの GetCacheAsync パターンのようなことを行う必要があります。このパターンでは、オブジェクトを同期的に構築し、非同期メソッドを待機する静的な非同期メソッドを作成します。これにより、コンテキストを待機して最後まで「アタッチ」することにより、適切な非同期チェーンが作成されます。

于 2012-04-20T08:53:34.577 に答える