CountdownEvent никогда не устанавливается на ноль

Мне нужно загрузить несколько текстов с разных URL-адресов, затем я использую CountDownEvent для обработки количества раз, когда мое событие Donwnload завершается, но дело в том, что мой CountDownEvent никогда не устанавливается на ноль, и это остается в ожидании.

Любая идея, что не так с этим кодом?

namespace WebApplication.AsyncCall
{
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Threading;

    public partial class _Default : System.Web.UI.Page
    {
        private CountdownEvent countDown = null;
        public CountdownEvent CountDown
        {
            get
            {
                if (this.countDown == null)
                {
                    this.countDown = new CountdownEvent(1);
                }

                return this.countDown;
            }
        }

        private List<string> text = null;
        public List<string> Text
        {
            get
            {
                if (this.text == null)
                {
                    this.text = new List<string>();
                }

                return this.text;
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            List<string> rssSources = new List<string>();

            rssSources.Add(@"http://news.yahoo.com/rss/entertainment");
            rssSources.Add(@"http://go.microsoft.com/fwlink/?linkid=84795&clcid=409");

            foreach (string uri in rssSources)
            {
                this.CountDown.AddCount();
                LoadSources(uri);
            }

            this.CountDown.Signal();
            this.CountDown.Wait();
        }

        private void LoadSources(string uri)
        {
            WebClient client = new WebClient();
            client.DownloadStringAsync(new Uri(uri, UriKind.Absolute));

            client.DownloadStringCompleted += (s, a) =>
            {
                if (a.Error == null && !a.Cancelled)
                {
                    this.Text.Add(a.Result);
                    this.CountDown.Signal();
                }
            };
        }
    }
}

person luis_laurent    schedule 28.11.2012    source источник
comment
Завершается ли асинхронный вызов до добавления слушателя, случайно? Попробуйте переместить client.DownloadStringAsync(new Uri(uri, UriKind.Absolute)); в конец метода LoadSources и посмотрите, сработает ли это.   -  person Jeff    schedule 28.11.2012
comment
Обратный вызов точно вызывается? РЕДАКТИРОВАТЬ: Кроме того, вы можете попробовать опросить значение CountDown в цикле while вместо использования this.CountDown.Wait();, чтобы помочь в отладке.   -  person Jeff    schedule 28.11.2012
comment
Если при загрузке произойдет какая-либо ошибка, CountDown не будет сообщено. Также возможно, что два обратных вызова будут выполняться одновременно в разных потоках, и вызов this.Text.Add будет вызываться одновременно двумя разными потоками. Это может привести к серьезным проблемам.   -  person Jim Mischel    schedule 28.11.2012
comment
спасибо за ваш совет, я добавлю оператор блокировки, чтобы избежать этого.   -  person luis_laurent    schedule 28.11.2012


Ответы (1)


Наконец-то я понял, как решить свою проблему, дело в том, что, несмотря на то, что я запускаю событие загрузки асинхронно, кажется, что они все еще выполняются в основном потоке, что означает, что this.CountDown.Wait() вызывается до того, как любая загрузка будет завершена, тогда мой this.CountDown не сигнализируется, поэтому this.CountDown никогда не устанавливается в ноль, и это остается в ожидании.

Вот что я сделал:

В foreach я заменил вызов метода LoadSources(uri) на ThreadPool.QueueUserWorkItem который ставит метод в очередь на выполнение. Метод выполняется, когда поток из пула становится доступным.

ThreadPool.QueueUserWorkItem(new WaitCallback(LoadSources), (object)uri);

Мне также пришлось изменить метод LoadSources, чтобы он соответствовал моим настройкам.

private void LoadSources(object uri)
{
    WebClient client = new WebClient();
    client.DownloadStringAsync(new Uri(uri.ToString(), UriKind.Absolute));

    client.DownloadStringCompleted += (s, a) =>
    {
        lock (thisLock)
        {
            try
            {
                if (a.Error == null && !a.Cancelled)
                {
                    this.Text.Add(a.Result);
                }
            }
            finally
            {
                this.CountDown.Signal();
            } 
        }
    };
}

Как видите, я добавил оператор блокировки, чтобы два или более потока не пытались одновременно вызвать this.Text.Add.

До этого я просто объявил частный объект для блокировки.

private Object thisLock = new Object();
person luis_laurent    schedule 28.11.2012