私は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);
}
LoadMyPage
Webサービスからデータを取得して画面にロードするため、完了までに時間がかかります。しかし、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";
}
}
これを試してみる...
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
の前に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がフリーズしなかったことを確認できます。
もしLoadMyPage
UIとやり取りするには、少し調整し直す必要があります。ViewModel
あるいはあなたが望む結果、そしてUIスレッド上ですべてを再びまとめる。
RetrieveFeedAsync
- pm_2Sleep
を使ってManualResetEvent(false).WaitOne(ms)
。私の答えがどのようなものではないのかわかりません。async
クリアしてもらえますか。 - Filip EkbergTask
非同期的に実行されますawaited
にItemListView_SelectionChanged
。 - Filip Ekberg
バックグラウンドスレッドは最も確実ですではないやり過ぎです。それこそまさにあなたがこの種の問題を扱う方法です。
UIスレッド上で長時間のタスクを実行しないでください。さもないと、UIが拘束されて応答しなくなります。これらをバックグラウンドスレッドで実行してから、そのスレッドにメインUIスレッドが終了したときに処理できるイベントを発生させます。
UIスレッドに何らかの進行状況インジケーターを表示することも便利です。ユーザーはそれを知りたい何かが起こっている。これにより、アプリが壊れたりフリーズしたりしていないことを安心させることができ、もう少し待つ必要があります。これが、すべてのWebブラウザになんらかの「ドキドキ」やその他の負荷インジケータがある理由です。
最も可能性の高い問題はそれですLoadMyPage
同期的に何かをしている。覚えて、async
バックグラウンドスレッドでコードを実行しないでください。デフォルトでは実際のすべてコードUIスレッド上で実行されます(非同期/待つFAQまたは私の非同期/イントロを待つ)したがって、非同期メソッドをブロックしても、呼び出し側スレッドはブロックされたままになります。
を見てみましょうLoadMyPage
。使っていますかawait
Webサービスを呼び出すには?データをUIに配置する前に、データの処理に時間がかかるのでしょうか。それはUIを圧倒していますか(多くのWindowsコントロールは、何千もの要素に到達するとスケーラビリティの問題を抱えています)?
LoadMyPage
電話するawait Task.Run(() => {}).ConfigureAwait(false)
呼び出す前にRetrieveFeedAsync
。何が起こるのですか? - Stephen Cleary
あなたの単純化されたコードの例を見て、私はあなたの問題があるべきすべてであると信じます外側待ち行列
次のコードブロックで:
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
foreach
完全にループ - pm_2