9

UI の応答性を維持するために非同期で実行する 2 つの方法があります。ただし、UI はまだハングしています。助言がありますか?

async void DoScrape()
    {
        var feed = new Feed();

        var results = await feed.GetList();
        foreach (var itemObject in results)
        {
            var item = new ListViewItem(itemObject.Title);
            item.SubItems.Add(itemObject.Link);
            item.SubItems.Add(itemObject.Description);
            LstResults.Items.Add(item);
        }
    }


    public class Feed
    {
        async public Task<List<ItemObject>> GetList()
        {
            var client = new WebClient();
            string content = await client.DownloadStringTaskAsync(new Uri("anyUrl"));
            var lstItemObjects = new List<ItemObject>();
            var feed = new XmlDocument();
            feed.LoadXml(content);
            var nodes = feed.GetElementsByTagName("item");

            foreach (XmlNode node in nodes)
            {
                var tmpItemObject = new ItemObject();
                var title = node["title"];
                if (title != null) tmpItemObject.Title = title.InnerText;
                var link = node["link"];
                if (link != null) tmpItemObject.Link = link.InnerText;
                var description = node["description"];
                if (description != null) tmpItemObject.Description = description.InnerText;
                lstItemObjects.Add(tmpItemObject);
            }
            return lstItemObjects;
        }
    }
4

3 に答える 3

12

より低いレベルでDownloadStringTaskAsync依存していると思われます。HttpWebRequest.BeginGetResponseこの場合、Web リクエストのセットアップは完全に非同期ではないことが知られています。厄介なことに (そして率直に言って愚かなことに)、非同期 WebRequest の DNS ルックアップ フェーズは同期的に実行されるため、ブロックされます。これがあなたが観察している問題かもしれないと思います。

以下に再現されているのは、ドキュメントの警告です。

BeginGetResponse メソッドでは、このメソッドが非同期になる前に、いくつかの同期セットアップ タスク (DNS 解決、プロキシ検出、TCP ソケット接続など) を完了する必要があります。結果として、このメソッドはユーザー インターフェイス (UI) スレッドで呼び出されるべきではありません。これは、通常は数秒かかる場合があるためです。Webproxy スクリプトが正しく構成されていない一部の環境では、これに 60 秒以上かかる場合があります。config file 要素の downloadTime 属性のデフォルト値は 1 分であり、これが潜在的な時間の遅延の大部分を占めています。

次の 2 つの選択肢があります。

  1. ワーカー スレッドからリクエストを開始します (高負荷下では、動作がブロックされるため、ThreadPool が枯渇するリスクがあります)。
  2. (慎重に) 要求を開始する前に、プログラムによる DNS ルックアップを実行します。これは非同期で実行できます。うまくいけば、要求はキャッシュされた DNS ルックアップを使用します。

まともなスループットを得るために、独自の適切な非同期 HTTP ライブラリを実装するという 3 番目の (そしてコストのかかる) オプションを選択しましたが、おそらくあなたの場合は少し極端です ;)

于 2011-07-17T19:47:11.230 に答える
5

非同期と並列を混同しているようです。どちらもタスクに基づいていますが、まったく異なります。asyncメソッドが並行して実行されると想定しないでください。そうではありません。

メインスレッドにメッセージポンプがない場合など、非同期エンジンが新しいスレッドをスピンアップすることを強制する理由がない限り、非同期はデフォルトで同じスレッドで動作します。asyncしかし、一般的に、キーワードは同じスレッドで実行されていると考える傾向があります。

WinForms を使用しているため、UI スレッドにはメッセージ ポンプがあります。したがって、上記のすべてのコードは UI スレッドで実行されます

ここでは並列処理を導入していないことを理解する必要があります。asyncキーワードを介して導入したのは、並列ではなく非同期操作です。DownloadStringTaskAsyncデータが到着するのを待つ必要がないへの 1 つの呼び出しを除いて、「UI をレスポンシブにする」ために何もしていません、それでもすべてのネットワーク処理 (DNS ルックアップなど) を実行する必要があります。 UI スレッド -- これが実行中の非同期操作です (基本的に、ダウンロードを待つ時間を「節約」します)。

UI の応答性を維持するには、UI スレッドを解放したまま、時間のかかる作業を別のスレッドに分割する必要があります。あなたはasyncキーワードでこれをしていません。

Task.Factory.StartNew(...)バックグラウンド処理を行うには、新しいスレッドを明示的にスピンアップするために使用する必要があります。

于 2011-07-18T02:02:52.240 に答える
4

リスト ビューに追加する項目はいくつありますか?

それを防ぐための措置を講じない限り、リストに項目を追加するたびに、WinForms リスト ビューは多くの処理を実行します。これには非常に時間がかかるため、100 個のアイテムを追加するだけでも数秒かかる場合があります。

BeginUpdateループを使用しEndUpdateて、終了するまで ListView の簿記を延期してみてください。

async void DoScrape()
{
    var feed = new Feed();

    var results = await feed.GetList();
    LstResults.BeginUpdate();  // Add this
    try
    {
        foreach (var itemObject in results)
        {
            var item = new ListViewItem(itemObject.Title);
            item.SubItems.Add(itemObject.Link);
            item.SubItems.Add(itemObject.Description);
            LstResults.Items.Add(item);
        }
    }
    finally
    {
        LstResults.EndUpdate();
    }
}

例外がある場合は、あらゆる種類の苦痛を避けるために、最後に try を使用する必要があります。

于 2011-07-17T19:52:53.640 に答える