Ожидание ManualResetEvent не освобождается после установки

Я загружаю два файла JSON из Интернета, после чего хочу разрешить загрузку двух страниц, но не раньше. Однако ManualResetEvent, который необходимо установить для загрузки страницы, никогда не «срабатывает». Хотя я знаю, что он устанавливается, WaitOne никогда не возвращается.

Метод запуска загрузок:

private void Application_Launching(object sender, LaunchingEventArgs e)
{
    PhoneApplicationService.Current.State["doneList"] = new List<int>();
    PhoneApplicationService.Current.State["manualResetEvent"] = new ManualResetEvent(false);

    Helpers.DownloadAndStoreJsonObject<ArticleList>("http://arkad.tlth.se/api/get_posts/", "articleList");
    Helpers.DownloadAndStoreJsonObject<CompanyList>("http://arkad.tlth.se/api/get_posts/?postType=webbkatalog", "catalog");
}

Метод загрузки, который устанавливает ManualResetEvent

public static void DownloadAndStoreJsonObject<T>(string url, string objName)
{
    var webClient = new WebClient();
    webClient.DownloadStringCompleted += (sender, e) => 
    {
        if (!string.IsNullOrEmpty(e.Result))
        {
            var obj = ProcessJson<T>(e.Result);
            PhoneApplicationService.Current.State[objName] = obj;


            var doneList = PhoneApplicationService.Current.State["doneList"] as List<int>;
            doneList.Add(0);

            if (doneList.Count == 2)    // Two items loaded
            {
                (PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).Set();  // Signal that it's done
            }
        }
    };

    webClient.DownloadStringAsync(new Uri(url));
}

Метод ожидания (в данном случае конструктор)

public SenastePage()
{
    InitializeComponent();

    if ((PhoneApplicationService.Current.State["doneList"] as List<int>).Count < 2)
    {
        (PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).WaitOne();
    }
    SenasteArticleList.ItemsSource =  (PhoneApplicationService.Current.State["articleList"] as ArticleList).posts;
}

Если я подожду, прежде чем пытаться получить доступ к этому конструктору, он легко пройдет оператор if и не попадет в WaitOne, но если я вызову его немедленно, я застряну, и он никогда не вернется...

Любые идеи?


person SamiHuutoniemi    schedule 20.10.2013    source источник
comment
Это тупик. Событие DownloadStringCompleted WebClient выполняется в потоке пользовательского интерфейса, в том самом потоке, который вы блокируете с помощью события WaitOne. В любом случае вам следует любой ценой избегать блокировки потока пользовательского интерфейса. Пока вы ожидаете данные, лучше отображать анимацию загрузки.   -  person Kevin Gosse    schedule 20.10.2013
comment
Можете ли вы зайти в отладчик, пока он находится в этом зависшем состоянии, и проверить переменные doneList.Count и manualResetEvent, чтобы увидеть, что они установлены в данный момент?   -  person Micah Zoltu    schedule 20.10.2013
comment
@KooKiz: Отображение заставки или нет, в какой-то момент я должен узнать, когда обе загрузки завершены. Разве мне все еще не нужно иметь ManualResetEvent?   -  person SamiHuutoniemi    schedule 20.10.2013
comment
@MicahCaldwell: я могу.   -  person SamiHuutoniemi    schedule 20.10.2013
comment
@SamiHuutoniemi Отобразите анимацию загрузки, а затем уведомите пользовательский интерфейс с помощью функции обратного вызова, когда загрузка будет завершена. Вы даже можете использовать задачи для такого сценария.   -  person Kevin Gosse    schedule 20.10.2013
comment
Также обратите внимание, что вы забыли обработать случаи ошибок. Если загрузка не удалась, DownloadStringCompleted не будет запущено, и событие ожидания никогда не будет установлено.   -  person Kevin Gosse    schedule 20.10.2013


Ответы (2)


Блокирование потока пользовательского интерфейса должно быть предотвращено любой ценой. Особенно при загрузке данных: не забывайте, что ваше приложение выполняется на телефоне с очень нестабильной сетью. Если для загрузки данных требуется две минуты, пользовательский интерфейс будет заморожен на две минуты. Это был бы ужасный пользовательский опыт.

Есть много способов предотвратить это. Например, вы можете сохранить ту же логику, но ожидать в фоновом потоке вместо потока пользовательского интерфейса:

public SenastePage()
{
    // Write the XAML of your page to display the loading animation per default
    InitializeComponent();

    Task.Factory.StartNew(LoadData);
}

private void LoadData()
{
    ((ManualResetEvent)PhoneApplicationService.Current.State["manualResetEvent"]).WaitOne();

    Dispatcher.BeginInvoke(() =>
    {
        SenasteArticleList.ItemsSource = ((ArticleList)PhoneApplicationService.Current.State["articleList"]).posts;

        // Hide the loading animation
    }
}

Это просто быстрый и грязный способ достичь желаемого результата. Вы также можете переписать свой код, используя задачи и используя Task.WhenAll для запуска действия, когда все они будут завершены.

person Kevin Gosse    schedule 20.10.2013

Возможно, есть проблема с логикой. В конструкторе SenastePage() вы ожидаете события set, только если количество doneList меньше двух. Однако вы не запускаете событие set, пока счетчик doneList не станет равным двум. Вы прослушиваете установленное событие еще до того, как оно сработает.

person Phillip Ngan    schedule 20.10.2013
comment
Разве это не главное? Если количество равно 2 или больше, загрузка завершена, и мне не нужно ждать. Если меньше 2, начинаю ждать. - person SamiHuutoniemi; 20.10.2013