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

5 に答える 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% ポジティブになるのは大変でした。

于 2012-08-17T22:40:29.453 に答える
4

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

LoadMyPage(and ) が次のようになっていると仮定し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;
}

TaskLoadMyPage返される は並行して実行されます。つまり、そのタスクの実行中に がフリーズすることUIはありません。を使用して結果を取得しTaskますawait。これにより、継続ブロックが作成されます。

したがって、この例では、何かを選択すると、ロード中は が無効になり、が完了ListViewすると再び有効になります。Taskボタンを押して UI がまだ応答していることを確認することで、UI がフリーズしていないことを確認できます。

が UI とやり取りする場合LoadMyPageは、それを少し再配置し、必要なViewModel結果または結果を返してから、UI スレッドですべてを再びまとめる必要があります。

于 2012-08-09T07:47:21.683 に答える
2

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

をご覧くださいLoadMyPageawaitWeb サービスの呼び出しに使用されていますか? UI にデータを配置する前に、データの高価な処理を行っていますか? UI は圧倒されますか (多くの Windows コントロールは、要素が数千になるとスケーラビリティの問題を抱えています)。

于 2012-08-09T10:26:59.427 に答える
2

単純化されたコード例を見ると、問題はawait 行の外側にあるすべてのものだと思います。

次のコード ブロックでは:

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 の後に来るコードは、await のに使用されていたのと同じスレッドで自動的に再開されるということです。これはコードを意味します

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

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

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

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

于 2012-08-14T16:00:07.747 に答える
2

バックグラウンド スレッドは、やり過ぎではありません。それがまさにこの種の問題を処理する方法です。

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

UI スレッドにある種の進行状況インジケーターを表示することも役立ちます。ユーザーは、何かが起こっていることを知りたいと思っています。これにより、アプリが壊れたりフリーズしたりしていないことを安心させ、もう少し待ってくれるでしょう。これが、すべての Web ブラウザーに何らかの「スロバー」またはその他の読み込みインジケーターがある理由です。

于 2012-08-09T07:34:03.320 に答える