Многопоточность в Silverlight - Как обрабатывать обратные вызовы? - PullRequest
0 голосов
/ 22 мая 2011

Я столкнулся с трудной ситуацией в своем приложении Silverlight. Мой клиент должен получить один раз три таблицы поиска из Службы. Как только все три коллекции будут полностью извлечены, заявка продолжится. Для простоты я показываю код только для одной справочной таблицы под названием «Страны».

Я думал запустить каждый вызов службы в отдельном потоке и использовать ManualResetEvent [] и WaitHandle.WaitAll () для синхронизации потоков.

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

Это сервисный вызов для поиска стран: SCMService:

    private LoadOperation<Country> _allCountriesLoadOperation;
    private Action<ObservableCollection<Country>> _getAllCountriesCallBack;

    public void GetAllCountries(Action<ObservableCollection<Country>> getAllCountriesCallback)
    {
          Context.Countries.Clear();
          var query = Context.GetCountriesQuery().OrderBy(c => c.CountryName);
          _getAllCountriesCallBack = getAllCountriesCallback;
          _allCountriesLoadOperation = Context.Load(query);
          _allCountriesLoadOperation.Completed += OnLoadCountriesCompleted;
    }

    private void OnLoadCountriesCompleted(object sender, EventArgs e)
    {
          _allCountriesLoadOperation.Completed -= OnLoadCountriesCompleted;
          var countries = new EntityList<Country>(Context.Countries, _allCountriesLoadOperation.Entities);
          _getAllCountriesCallBack(countries);
    }

Это код, который я запускаю для вызова службы выше: Класс контроллера:

        public void OnGetAllCountries()
        {
            _service.GetAllCountries(GetCountriesCallback);
        }

        private void GetCountriesCallback(ObservableCollection<Country> countries)
        {
            if (countries != null)
            {
                if (Countries == null)
                {
                    Countries = countries;
                    _manualResetEvents[0].Set();
                }
            }
        }

        Controller()
        {
             //OnGetAllCountries();  This line alone would work fine. But I need to                    
             //wait and make sure the callback is finished before continuing. 
             // Hence my multi threading approach below:

             new Thread(OnGetAllCountries).Start();
             WaitHandle.WaitAll(_manualResetEvents);
        }

Обновление: Я обновил код, чтобы сделать его более понятным, код и обратные вызовы работают, если они выполняются в потоке пользовательского интерфейса. Но мои темы застряли, если бы я сделал это многопоточным. см. комментарии к коду

Что я делаю не так? Имеет ли смысл моя стратегия?

высоко ценится,

Ответы [ 3 ]

0 голосов
/ 22 мая 2011

Хорошо, я думаю, вам, возможно, придется изучить шаблон View Model, чтобы понять, как он должен обрабатывать такие ситуации, но если я правильно понимаю, ответом на ваш вопрос может быть следующая стратегия.

Забудьте о блокировке mainпоток - он не работает, вы вводите элемент ожидания, который отображается, и пока он показан, другие элементы программы, например, недоступны, пока определенное значение, указывающее, что все объекты загружены, не будет истинным, и оно будет вычислено в отдельном потоке.это, в свою очередь, запустит 3 потока и будет блокировать до тех пор, пока не будут загружены все 3.

Или, если вы используете более ясный подход ViewModel, вы вводите свойство, которое будет представлять собой комбинацию всех 3 загруженных элементов, например

* 1006.*

и в методах get для этих FirstLoaded, SecondLoaded и ThirdLoaded вы выполняете асинхронную загрузку коллекций, как только они загружены, вы NotifyPropertyChanged для них и проверяете, нужно ли уведомлять, что AllLoaded также имеет значение true, когда все 3 установлены в true,Как только для AllLoaded установлено значение true, ваши элементы ожидания скрываются, и программные элементы становятся доступными для работы.

Этот подход более MVVM и Silverlightish, но ваш подход также может работать, если вы не выполняете блок в основном потоке SL.

0 голосов
/ 04 августа 2011

Я делаю что-то подобное, но с помощью ThreadPool.QueueUserWorkItem я загружаю 3 коллекции объектов Scans, Actions и Steps.

  1. класс, тянущий одну сущность, делает это в отдельном потоке, используя ThreadPool.
  2. этот класс должен иметь общедоступный обратный вызов для уведомления загрузчика о том, что задача выполнена.
  3. У меня столько же классов, сколько и сущностей, которые мне нужно вытащить.

в классе Loader я регистрирую все обратные вызовы от 3-х сущностей, и там я устанавливаю manualresetevents следующим образом:

    void Scans_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "ScanItems")
        {
            Debug.WriteLine("Scans Done =====");     
            manualEvents[(int)ItemDone.Scans].Set(); 
        }
    }

Внутри загрузчика я жду всех внутри другого потока, используя ThreadPool, например:

        manualEvents = new ManualResetEvent[3];
        manualEvents[(int)ItemDone.Scans] = new ManualResetEvent(false);
        manualEvents[(int)ItemDone.Actions] = new ManualResetEvent(false);
        manualEvents[(int)ItemDone.AssySteps] = new ManualResetEvent(false);

        ThreadPool.QueueUserWorkItem((obj) =>
        {
            Scans.Get(WipID, WC);
            Actions.Get(WipID, WC);
            AssySteps.Get(WipID, WC);
            if (WaitHandle.WaitAll(manualEvents, new TimeSpan(0, 0, 5)))
            {
                int InstIndex = 0;
                Instructions = new List<Instruction>();
                AssySteps.AssyStepsItems.ForEach(s =>{Some code here....});
            }
        }

Этот способ хорошо работает для меня, у меня есть его тест, и производительность намного лучше, чем в последовательном порядке.домой это помогает.С уважением.

0 голосов
/ 22 мая 2011

Это не совсем понятно из вашего кода, но кажется, что вы пытаетесь заблокировать основной поток Silverlight, выполнив WaitHandle.WaitAll. Вы не можете сделать это в Silverligt.

Вместо этого вы должны обновить содержимое вашего пользовательского интерфейса с помощью обратных вызовов. Вы можете сделать это несколькими способами, и использование привязки данных может вам помочь.

Вы можете использовать модель представления, которая привязана к представлению и обновляется независимыми обратными вызовами. Только при выполнении третьего обратного вызова модель представления будет полностью обновлена. На это может указывать свойство «ready» в модели представления, которое можно использовать для скрытия и отображения частей пользовательского интерфейса.

Если вы хотите «заблокировать» приложение до тех пор, пока не будут получены все данные, вам нужно будет отобразить индикатор занятости (например, найденный в Silverlight Toolkit ). Затем вы должны скрыть индикатор занятости, когда все данные будут получены.

Если вы предпочитаете обрабатывать службу так же, как и сейчас, вам нужно вызывать службы из фонового потока или задачи. Затем этот поток / задание может блокироваться таким же образом, как показано в коде, за исключением того, что вы делаете это в фоновом потоке, а не в основном потоке. После выполнения всех трех обратных вызовов вы можете обновить пользовательский интерфейс из фонового потока / задачи.

Если вы предпочитаете использовать поток, а не задачу, я предлагаю вам использовать поток из пула потоков вместо создания собственного.

Если вы новичок в асинхронной природе модели программирования Silverlight, я предлагаю вам пройтись по образцу кода, чтобы понять, как обновить пользовательский интерфейс из одного вызова службы. Затем вы можете распространить это на многие сервисные вызовы таким же образом, как вы делаете сейчас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...