Faking Synch WCF calls in WP7
January 10, 2012 2 Comments
So you can’t do synchronous WCF calls in Windows Phone 7. I have a use case where I do a WCF call to get all of the data to set up a game board of the phone game I am writing. I don’t want the application to progress until the board is set up, so I need it as a blocking call. In addition, the WCF call is 5-6 layers deep in my class hierarchy. My first idea was just to use a non-thread-safe variable from the Main UI thread and set the flag when the asynch call/thread returned:
private List<ScenarioTile> GetScenarioTilesFromWebService(int scenarioId) { CallbackComplete = false; PanzerProxy.PanzerClient panzerClient = new PanzerProxy.PanzerClient(); panzerClient.GetScenarioTilesCompleted += new EventHandler<PanzerProxy.GetScenarioTilesCompletedEventArgs>(panzerClient_GetScenarioTilesCompleted); panzerClient.GetScenarioTilesAsync(scenarioId); while (CallbackComplete == false) { Thread.Sleep(1000); } return ScenarioTiles; } void panzerClient_GetScenarioTilesCompleted(object sender, PanzerProxy.GetScenarioTilesCompletedEventArgs e) { ScenarioTiles = new List<ScenarioTile>(); List<PanzerProxy.ScenarioTile> proxyTiles = e.Result as List<PanzerProxy.ScenarioTile>; foreach (PanzerProxy.ScenarioTile proxyScenarioTile in proxyTiles) { ScenarioTiles.Add(ConvertProxyScenarioTileToScenarioTile(proxyScenarioTile)); } CallbackComplete = true; }
The problem is that, well, it doesn’t work. The app just hangs. I then thought of using a ManualResetEvent or AutoResetEvent class to trigger like this:
ManualResetEvent manualResetEvent = new ManualResetEvent(false); private List<ScenarioTile> GetScenarioTilesFromWebService(int scenarioId) { PanzerProxy.PanzerClient panzerClient = new PanzerProxy.PanzerClient(); panzerClient.GetScenarioTilesCompleted += new EventHandler<PanzerProxy.GetScenarioTilesCompletedEventArgs>(panzerClient_GetScenarioTilesCompleted); panzerClient.GetScenarioTilesAsync(scenarioId); manualResetEvent.WaitOne(); return ScenarioTiles; } void panzerClient_GetScenarioTilesCompleted(object sender, PanzerProxy.GetScenarioTilesCompletedEventArgs e) { ScenarioTiles = new List<ScenarioTile>(); List<PanzerProxy.ScenarioTile> proxyTiles = e.Result as List<PanzerProxy.ScenarioTile>; foreach (PanzerProxy.ScenarioTile proxyScenarioTile in proxyTiles) { ScenarioTiles.Add(ConvertProxyScenarioTileToScenarioTile(proxyScenarioTile)); } manualResetEvent.Set(); }
Still no luck. I then ran across this thread – basically you can’t do anything to block the Main UI thread. Chong’s solution was to launch a 3rd thread from the secondary thread. That seemed to me as overly complex and smelled of brittle code. I then took a step back and realized I just had to architect my solution to get the same effect. Basically, once the user hits the LOAD button, the MAINUI thread is done. However, the page also subscribes to an event that the business logic raises. Once the event fires, then the app progresses to the next page.
So instead of this:
private void SetupScenario(int scenarioId) { Game.CurrentScenarioId = scenarioId; Game.BoardFactory.PopulateBoard(scenarioId); Game.CurrentBoard = Game.BoardFactory.Board; Game.Turns = Game.TurnFactory.GetTurnsForAScenario(scenarioId); this.NavigationService.Navigate(new Uri(@"/Briefing.xaml", UriKind.Relative)); }
I have this:
private void SetupScenario(int scenarioId) { Game.CurrentScenarioId = scenarioId; Game.BoardFactory.BoardLoaded += new EventHandler<EventArgs>(BoardFactory_BoardLoaded); Game.BoardFactory.PopulateBoard(scenarioId); Game.CurrentBoard = Game.BoardFactory.Board; Game.Turns = Game.TurnFactory.GetTurnsForAScenario(scenarioId); } void BoardFactory_BoardLoaded(object sender, EventArgs e) { this.NavigationService.Navigate(new Uri(@"/Briefing.xaml", UriKind.Relative)); }
The event is defined as simple as it comes:
public event EventHandler<EventArgs> BoardLoaded;
and
void TileFactory_TilesLoaded(object sender, EventArgs e) { Tiles = Game.TileFactory.Tiles; Hexes = Game.HexFactory.GetHexes(Tiles); foreach (Hex hex in Hexes) { Board.MainCanvas.Children.Add(hex); } SetBoardDimensions(Board, Tiles); BoardLoaded(null, null); }
Basically I just bubbled up the WCF asych event up from the lowest level to the UI page.
Great post- the difference between this scenario and my contact search one you linked to, though, is that I wanted my method to return an object (whereas here some stuff just gets executed) – I might have been able to pass in some other object, maybe some sort of composite request type, to get around having to spawn a third thread, but totally agree with you that it’s not something anyone would really want to be doing – if you’ve got an alternative, would love to hear it 🙂
Are you also by any chance making a panzer general app for wp7? 😀
Yeah – I created a Panzer General App for Window Phone. It ‘barely’ works – I got tired of coding it so I put it onto AppHub. If you want to pick up the mantle, the source code is on Githib: https://github.com/jamessdixon/PanzerGeneralPortable