开发者

Multithreading in Silverlight - How to handle Callbacks?

I have come across a difficult situation in my silverlight app. My client has to retrieve once three lookup tables from the Service. Once all three collections are fully retrieved the application shall continue. For simplicity I only show the code for one lookup table called "Countries".

I was thinking to run each service call on a different thread and utilize the ManualResetEvent[] and WaitHandle.WaitAll() to sync the threads.

But due the async nature of Silverlight the Service call uses a callback to tell the caller that a single collection has now been loaded. I can't simply get this working and am confused.

This is the service call to retrieve the Countries: 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);
    }

This is the code I am running to call the service above: Controller Class:

        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        开发者_JS百科            
             //wait and make sure the callback is finished before continuing. 
             // Hence my multi threading approach below:

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

Update: I have updated the code to make it more clear, the code and callbacks are working if ran on the UI thread. But my threads get stuck if I did this multithreaded. see code comments

What am I doing wrong? Does my strategy makes sense?

Highly appreciated,


It is not entirely clear from your code, but it seems that you are trying to block the main Silverlight thread by doing a WaitHandle.WaitAll. You cannot do that in Silverligt.

Instead you should update the contents of you user interface from the callbacks. You can do that in multiple ways and using data-binding may help you.

You can use a view-model that is databound to a view and is updated by independent callbacks. Only when the third callback is executed will the view-model be fully updated. That can be signaled by a "ready" property on the view-model that can be used to hide and show parts of the user interface.

If you want to "block" the application until all data has been retrieved you will have to display a busy indicator (for instance found in the Silverlight Toolkit). You should then hide the busy indicator when all data has been retrieved.

If you prefer to handle the service as you do now you need to call the services from a background thread or task. This thread/task can then block in the same way as you have shown in your code except you do it on a background thread and not the main thread. When all three callbacks have executed can you update the user interface from the background thread/task.

If you prefer to use a thread and not a task I suggest that you use a thread from the threadpool instead of creating your own.

If you are new to the asynchronous nature of the Silverlight programming model I suggest that you work your way through sample code to understand how to update the user interface from a single service call. You can then extend that to many service calls in the same manner as you are doing now.


Okay I think you might have to explore View Model pattern to understand how it is supposed to handle such situations, but if I understand correctly the answer to your question could be the following strategy.

Forget blocking main thread - it doesn't work, you introduce a wait element that is shown & while it is shown other program elements arent accessible for example, until certain value indicating that all objects are loaded is true, and it will be computed in a separate thread, that in turn will launch 3 threads and will block until all 3 are loaded.

Or if you use more clear ViewModel approach you introduce property that will be combination of all 3 elements loaded, like

AllLoaded { get{return FirstLoaded && SecondLoaded && ThirdLoaded}} , 

and in get methods for those FirstLoaded,SecondLoaded and ThirdLoaded you perform asynchronous load of collections, once they are loaded you NotifyPropertyChanged for them and check whether you need to notify that AllLoaded is true too , when all 3 are set to true. Once AllLoaded is set to true, your wait element hides and program elements become available to work.

This approach is more MVVM & Silverlightish but your approach can work too if you dont perform block on main SL thread.


Im doing something similar but using the ThreadPool.QueueUserWorkItem I load 3 collections of Scans, Actions, and Steps objects.

  1. the class pulling one entity does it in a separated thread by using the ThreadPool.
  2. this class should have a public callback to notify the loader the task is done.
  3. I have as many similar classes as entities I have to pull.

in the Loader class I register all the callbacks from the 3 entities and there I set the manualresetevents like this:

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

Inside the loader I wait for all inside another thread using the ThreadPool like this:

        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....});
            }
        }

This way is working fine for me, I have benchmark it and the performances is much better than doing that in a sequential order. home this helps. regards.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜