6

私はWindows Metroアプリケーションを開発していますが、UIが応答しなくなるという問題を抱えています。私が言うことができる限り、原因は以下の通りです:

    <ListView
...
        SelectionChanged="ItemListView_SelectionChanged"            
...

このイベントはここで処理されます。

    async void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (this.UsingLogicalPageNavigation()) this.InvalidateVisualState();

        MyDataItem dataItem = e.AddedItems[0] as MyDataItem;
        await LoadMyPage(dataItem);
    }

    private async Task LoadMyPage(MyDataItem dataItem)
    {            
        SyndicationClient client = new SyndicationClient();
        SyndicationFeed feed = await client.RetrieveFeedAsync(new Uri(FEED_URI));                    

        string html = ConvertRSSToHtml(feed)
        myWebView.NavigateToString(html, true);            
    }

LoadMyPageWebサービスからデータを取得して画面にロードするため、完了までに時間がかかります。しかし、UIがそれを待っているように見えます。私の推測では、上記のイベントが完了するまでです。

だから私の質問です:私はこれについて何ができますか?私が引っ掛けることができるよりよいイベントがありますか、またはこれを処理する別の方法がありますか?私はバックグラウンドタスクを開始することを考えました、しかしそれは私にとってやり過ぎのようです。

編集:

この問題の規模を明確にするために、最大3〜4秒で応答しない状態にしています。これは決して長期の仕事ではありません。

編集:

私は以下の提案のいくつかを試してみました、しかし、全体からのコールスタックSelectionChanged関数はasync / awaitを使用しています。私はこの声明にそれを追跡しました:

myFeed = await client.RetrieveFeedAsync(uri);

それが完了するまで処理を続けているように見えません。

編集:

私はこれが戦争になっていることを理解しています。平和だが、以下は空白の地下鉄アプリとボタンを使った問題の再現である。

XAML:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel>
        <Button Click="Button_Click_1" Width="200" Height="200">test</Button>
        <TextBlock x:Name="test"/>
    </StackPanel>
</Grid>

コードビハインド:

    private async void Button_Click_1(object sender, RoutedEventArgs e)
    {
        SyndicationFeed feed = null;

        SyndicationClient client = new SyndicationClient();
        Uri feedUri = new Uri(myUri);

        try
        {
            feed = await client.RetrieveFeedAsync(feedUri);

            foreach (var item in feed.Items)
            {       
                test.Text += item.Summary.Text + Environment.NewLine;                    
            }
        }
        catch
        {
            test.Text += "Connection failed\n";
        }
    }


  • 「無反応」とはどういう意味ですか。ページにボタンを配置してマウスオーバーした場合、マウスオーバーの動作が見られますか?あなたはそのページとまったく対話できますか? LoadPage中にページを移行しようとしていますか? RetrieveFeedAsyncメソッドが完了するのにしばらく時間がかかる場合、それはUIをロックしませんが、何も起こらないように見えます。 - Shawn Kendrot
  • 画面上のコントロールが反応しません。マウスは動きますが、アプリのUIは一時的に使えません。 - pm_2
  • 返されたフィードにはいくつの項目がありますか。 - Stephen Cleary
  • 上記の例では、50 ...がありますが、削除しても同じ動作になります。foreach完全にループ - pm_2
  • LoadMyPageのコードがどこにも表示されないのですが…含めてください。理想的には、これを短くしてください。コンプリート問題を説明するプログラム。 - Jon Skeet

5 답변


5

これを試してみる...

SyndicationFeed feed = null;

SyndicationClient client = new SyndicationClient();

var feedUri = new Uri(myUri);

try {
    var task = client.RetrieveFeedAsync(feedUri).AsTask();

    task.ContinueWith((x) => {
        var result = x.Result;

        Parallel.ForEach(result.Items, item => {
            Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
            () =>
            {
                test.Text += item.Title.Text;
            });
       });     
   });
}
catch (Exception ex) { }

私は自分のマシンでGridアプリテンプレートを使ってボタンをアプリに追加することで試しました。ページのタイトルを問題なく更新しながら、アイテムのグリッドを前後にスクロールできます。それほど多くのアイテムを持っていなかったけれども、それは本当に速く動いたので、それは100%ポジティブであるのは難しいことでした。


  • これはうまくいくForEach) - ありがとう。私は次のような印象を受けました。await事実上これと同じことをした - だからこれは何をするのかawaitしないのですか? - pm_2
  • awaitを使用すると、コンパイラーは非同期呼び出しの完了に続くコードを自動的にUIスレッドにマーシャリングします。これを達成するためにタスクはバックグラウンドで使用されますが、コンパイラーの魔法はバックグラウンド処理の機会をメインスレッドに戻します。私のコードでは、UIスレッドに戻るのではなく、タスクを明示的に制御し、それを使用して独自のスレッドで作業を続けます。ループしているデータの量とUIの更新方法によっては、Parallel.ForEachをそのまま使用することをお勧めします。 - Jeff Brand
  • そこに例外が漏れているのではありませんか。タスクはすぐに開始され、継続は、例外処理があるのと同じ同期コンテキストにはありません。 - Henrik

4

あなたが使っているのでawaitの前にLoadMyPage私はそれがコンパイルされ、それがTask。それを考えると、私は小さな例を作成しました。

それを仮定しましょうLoadMyPage(そしてSleep())このようになります:

public Task<string> LoadMyPage()
{
    return Task<string>.Factory.StartNew(() =>
                                                {
                                                    Sleep(3000);
                                                    return "Hello world";
                                                });
}
static void Sleep(int ms)
{
    new ManualResetEvent(false).WaitOne(ms);
}

そしてそれはXAMLこのようになります:

<StackPanel>
    <TextBlock x:Name="Result" />
    <ListView x:Name="MyList" SelectionChanged="ItemListView_SelectionChanged">
        <ListViewItem>Test</ListViewItem>
        <ListViewItem>Test2</ListViewItem>
    </ListView>
    <Button>Some Button</Button>
    <Button>Some Button2</Button>
</StackPanel>

それから、SelectionChangedこのようなイベントハンドラ

private async void ItemListView_SelectionChanged(object sender,
                                                 SelectionChangedEventArgs e)
{
    MyList.IsEnabled = false;
    var result = await LoadMyPage();

    Result.Text = result;

    MyList.IsEnabled = true;
}

TaskそれLoadMyPage戻り値は並列に実行されます。つまり、そのタスクが実行されているときは、UI凍ってはいけません。今からその結果を得るためにTaskあなたが使うawait。これにより継続ブロックが作成されます。

この例では、何かを選択すると、ListViewロード時間全体にわたって無効になり、Task終わりました。ボタンがまだ反応していることを確認するためにボタンを押すことで、UIがフリーズしなかったことを確認できます。

もしLoadMyPageUIとやり取りするには、少し調整し直す必要があります。ViewModelあるいはあなたが望む結果、そしてUIスレッド上ですべてを再びまとめる。


  • 残念ながら、非同期メソッドを使用しているため、これを行うことはできませんRetrieveFeedAsync - pm_2
  • -1:質問は非同期の問題で、時間のかかるアイテムの問題ではなく、WinRTではスリープできません。 - Henrik
  • @ Henrik、私の答えを読み、もう一度コードサンプルをチェックしてください。シミュレートしていますSleepを使ってManualResetEvent(false).WaitOne(ms)。私の答えがどのようなものではないのかわかりません。asyncクリアしてもらえますか。 - Filip Ekberg
  • 私のポイントはあなたがあなたのスレッドをブロックしているということです。 - Henrik
  • @ヘンリック、いいえ?のTask非同期的に実行されますawaitedItemListView_SelectionChanged。 - Filip Ekberg

2

バックグラウンドスレッドは最も確実ですではないやり過ぎです。それこそまさにあなたがこの種の問題を扱う方法です。

UIスレッド上で長時間のタスクを実行しないでください。さもないと、UIが拘束されて応答しなくなります。これらをバックグラウンドスレッドで実行してから、そのスレッドにメインUIスレッドが終了したときに処理できるイベントを発生させます。

UIスレッドに何らかの進行状況インジケーターを表示することも便利です。ユーザーはそれを知りたい何かが起こっている。これにより、アプリが壊れたりフリーズしたりしていないことを安心させることができ、もう少し待つ必要があります。これが、すべてのWebブラウザになんらかの「ドキドキ」やその他の負荷インジケータがある理由です。


2

最も可能性の高い問題はそれですLoadMyPage同期的に何かをしている。覚えて、asyncバックグラウンドスレッドでコードを実行しないでください。デフォルトでは実際のすべてコードUIスレッド上で実行されます(非同期/待つFAQまたは私の非同期/イントロを待つ)したがって、非同期メソッドをブロックしても、呼び出し側スレッドはブロックされたままになります。

を見てみましょうLoadMyPage。使っていますかawaitWebサービスを呼び出すには?データをUIに配置する前に、データの処理に時間がかかるのでしょうか。それはUIを圧倒していますか(多くのWindowsコントロールは、何千もの要素に到達するとスケーラビリティの問題を抱えています)?


  • 最新の編集を見てください - それがしている最も高価なことはウェブへの呼び出しです。 1〜5秒かかりますが、そこにいる間はUIが停止しています。 - pm_2
  • このテストを試してください。LoadMyPage電話するawait Task.Run(() => {}).ConfigureAwait(false)呼び出す前にRetrieveFeedAsync。何が起こるのですか? - Stephen Cleary
  • 違いはありません - pm_2
  • 投稿してくださいすべてこの再現に必要なコード - Stephen Cleary
  • 上記の編集を参照してください - pm_2

2

あなたの単純化されたコードの例を見て、私はあなたの問題があるべきすべてであると信じます外側待ち行列

次のコードブロックで:

private async void Button_Click_1(object sender, RoutedEventArgs e)
    {
        SyndicationFeed feed = null;

        SyndicationClient client = new SyndicationClient();
        Uri feedUri = new Uri(myUri);

        try
        {
            feed = await client.RetrieveFeedAsync(feedUri);

            foreach (var item in feed.Items)
            {       
                test.Text += item.Summary.Text + Environment.NewLine;                    
            }
        }
        catch
        {
            test.Text += "Connection failed\n";
        }
    }

のみバックグラウンドスレッドで実行されている行はその行です。

feed = await client.RetrieveFeedAsync(feedUri);

すべてそのブロック内の他のコード行がUIスレッドで実行されています。

ボタンクリックハンドラが非同期としてマークされているからといって、その中のコードがUIスレッド上で実行されないわけではありません。実際、イベントハンドラはUIスレッド上で起動します。そのため、SyndicationClientを作成してUriを設定することは、UIスレッドで行われます。

多くの開発者が気づいていないことは、どんなコードでも来るということです。後にawaitは自動的に使用中のスレッドと同じスレッドで再開します待っています。これはコードを意味します

            foreach (var item in feed.Items)
            {       
                test.Text += item.Summary.Text + Environment.NewLine;                    
            }

はUIスレッドで実行されています。

これは、Dispatcher.Invokeを実行して更新する必要がないという点で便利です。test.Textしかし、それはまた、アイテムをループしたり文字列を連結したりしている間、UIスレッドをブロックしていることを意味します。

あなたの(単純化されたとはいえ)例では、バックグラウンドスレッドでこの作業を行う最も簡単な方法は、SyndicationClientに別のメソッドを呼び出すことです。RetrieveFeedAsStringAsync;それからSyndicationClientはそれ自身の仕事の一部としてストリングのダウンロード、ループおよび連結をすることができる。そのタスクが完了した後、UIスレッドで実行される唯一のコード行はテキストをTextBoxに割り当てることです。


  • しかし、完全に削除しても同じ動作になります。foreach()ループ。 - pm_2

リンクされた質問


関連する質問

最近の質問