From a191732a53999de25f8a05264b46dc002576e98c Mon Sep 17 00:00:00 2001 From: jorgensigvardsson Date: Mon, 13 Apr 2015 23:39:55 +0200 Subject: [PATCH 1/2] + Store app API is now async - LogicCore.AsyncAlgorithmCompute is disregarded. + DispatcherHelper bug fix: grab a safe handle to the UI thread dispatcher. + WPF control bug fix: cancellation token source was not disposed. --- Examples/METRO.SimpleGraph/MainPage.xaml.cs | 37 ++++- Examples/METRO.SimpleGraph/Models/CurvedEr.cs | 3 +- GraphX.Controls/Controls/GraphArea.cs | 1 + GraphX.METRO.Controls/Controls/GraphArea.cs | 136 +++++++++++------- .../Models/DispatcherHelper.cs | 17 +-- 5 files changed, 121 insertions(+), 73 deletions(-) diff --git a/Examples/METRO.SimpleGraph/MainPage.xaml.cs b/Examples/METRO.SimpleGraph/MainPage.xaml.cs index e00ee59..32bcd2b 100644 --- a/Examples/METRO.SimpleGraph/MainPage.xaml.cs +++ b/Examples/METRO.SimpleGraph/MainPage.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -51,15 +52,32 @@ namespace METRO.SimpleGraph zc.ZoomToFill(); } - void butGenerate_Click(object sender, RoutedEventArgs e) + private async void butGenerate_Click(object sender, RoutedEventArgs e) { GraphAreaExample_Setup(); - graph.GenerateGraph(true); + + try + { + await graph.GenerateGraphAsync(true); + } + catch (OperationCanceledException) + { + // User may have canceled + } } - void butRelayout_Click(object sender, RoutedEventArgs e) + async void butRelayout_Click(object sender, RoutedEventArgs e) { - graph.RelayoutGraph(); + try + { + var t0 = DateTime.Now; + await graph.RelayoutGraphAsync(); + Debug.WriteLine("Time elapsed: {0}", DateTime.Now - t0); + } + catch (OperationCanceledException) + { + // User may have canceled + } } void cboxEdgeRouting_SelectionChanged(object sender, SelectionChangedEventArgs e) @@ -80,12 +98,19 @@ namespace METRO.SimpleGraph graph.LogicCore.DefaultLayoutAlgorithm = (LayoutAlgorithmTypeEnum) cboxLayout.SelectedItem; } - void MainPage_Loaded(object sender, RoutedEventArgs e) + async void MainPage_Loaded(object sender, RoutedEventArgs e) { InitialSetup(); GraphAreaExample_Setup(); - graph.GenerateGraph(true); + try + { + await graph.GenerateGraphAsync(true); + } + catch (OperationCanceledException) + { + // User may have canceled + } //graph.RelayoutGraph(true); //zc.ZoomToFill(); diff --git a/Examples/METRO.SimpleGraph/Models/CurvedEr.cs b/Examples/METRO.SimpleGraph/Models/CurvedEr.cs index 6d40feb..3f06c17 100644 --- a/Examples/METRO.SimpleGraph/Models/CurvedEr.cs +++ b/Examples/METRO.SimpleGraph/Models/CurvedEr.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using GraphX; using GraphX.GraphSharp.Algorithms.EdgeRouting; using GraphX.Measure; @@ -18,7 +19,7 @@ namespace InteractiveGraph.Models _curveOffset = prms != null ? prms.VerticalCurveOffset : 20; } - public override void Compute() + public override void Compute(CancellationToken cancellationToken) { EdgeRoutes.Clear(); foreach (var edge in _graph.Edges) diff --git a/GraphX.Controls/Controls/GraphArea.cs b/GraphX.Controls/Controls/GraphArea.cs index dbfcfed..90dab8c 100644 --- a/GraphX.Controls/Controls/GraphArea.cs +++ b/GraphX.Controls/Controls/GraphArea.cs @@ -831,6 +831,7 @@ namespace GraphX // Wait, but don't block the dispatcher, because the background task might be trying to execute on the UI thread. Await(_layoutTask); + _layoutCancellationSource.Dispose(); _layoutCancellationSource = null; _layoutTask = null; } diff --git a/GraphX.METRO.Controls/Controls/GraphArea.cs b/GraphX.METRO.Controls/Controls/GraphArea.cs index 369b2f8..be8b53a 100644 --- a/GraphX.METRO.Controls/Controls/GraphArea.cs +++ b/GraphX.METRO.Controls/Controls/GraphArea.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using Windows.Foundation; using Windows.UI.Xaml; @@ -32,7 +33,7 @@ namespace GraphX public static readonly DependencyProperty LogicCoreProperty = DependencyProperty.Register("LogicCore", typeof(IGXLogicCore), typeof(GraphArea), new PropertyMetadata(null, logic_core_changed)); - private static void logic_core_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static async void logic_core_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { //automaticaly assign default file service provider if(e.NewValue != null && ((IGXLogicCore)e.NewValue).FileServiceProvider == null) @@ -43,16 +44,16 @@ namespace GraphX switch (graph.LogicCoreChangeAction) { case LogicCoreChangedAction.GenerateGraph: - graph.GenerateGraph(); + await graph.GenerateGraphAsync(); break; case LogicCoreChangedAction.GenerateGraphWithEdges: - graph.GenerateGraph(true); + await graph.GenerateGraphAsync(true); break; case LogicCoreChangedAction.RelayoutGraph: - graph.RelayoutGraph(); + await graph.RelayoutGraphAsync(); break; case LogicCoreChangedAction.RelayoutGraphWithEdges: - graph.RelayoutGraph(true); + await graph.RelayoutGraphAsync(true); break; default: break; @@ -520,21 +521,9 @@ namespace GraphX #endregion #region RelayoutGraph() - - private bool IsCalculating; - private object calcLocker = new object(); - - private async Task _relayoutGraph(bool generateAllEdges = false, bool standalone = true) + private Task _relayoutGraph(CancellationToken cancellationToken, bool generateAllEdges = false, bool standalone = true) { - //lock to GraphArea instance and check if calculation is already running - lock (calcLocker) - { - //do not allow new calculation to begin until previous isn't finished - if (IsCalculating) return; - IsCalculating = true; - } - - try + return Task.Run(async () => { Dictionary vertexSizes = null; IExternalLayout alg = null; //layout algorithm @@ -543,12 +532,12 @@ namespace GraphX IExternalEdgeRouting eralg = null; var result = false; - await DispatcherHelper.CheckBeginInvokeOnUi(new Action(() => + await DispatcherHelper.CheckBeginInvokeOnUi(() => { if (LogicCore == null) throw new GX_InvalidDataException("LogicCore -> Not initialized!"); if (LogicCore.Graph == null) - throw new GX_InvalidDataException("LogicCore -> Graph property is not set!"); + throw new GX_InvalidDataException("LogicCore -> Graph property is not set!"); if (_vertexlist.Count == 0) return; // no vertexes == no edges @@ -571,13 +560,13 @@ namespace GraphX //setup Edge Routing algorithm eralg = LogicCore.GenerateEdgeRoutingAlgorithm(DesiredSize.ToGraphX()); result = true; - })); + }); if (!result) return; IDictionary resultCoords; if (alg != null) { - alg.Compute(); + alg.Compute(cancellationToken); OnLayoutCalculationFinished(); //if (Worker != null) Worker.ReportProgress(33, 0); //result data storage @@ -594,13 +583,13 @@ namespace GraphX { //generate rectangle data from sizes var coords = resultCoords; - UpdateLayout(); await DispatcherHelper.CheckBeginInvokeOnUi(() => { + UpdateLayout(); rectangles = GetVertexSizeRectangles(coords, vertexSizes, true); }); overlap.Rectangles = rectangles; - overlap.Compute(); + overlap.Compute(cancellationToken); OnOverlapRemovalCalculationFinished(); resultCoords = new Dictionary(); foreach (var res in overlap.Rectangles) @@ -637,13 +626,14 @@ namespace GraphX if (MoveAnimation.VertexStorage.Count > 0) MoveAnimation.RunVertexAnimation(); - + foreach (var item in _edgeslist.Values) MoveAnimation.AddEdgeData(item); if (MoveAnimation.EdgeStorage.Count > 0) MoveAnimation.RunEdgeAnimation(); - + } + UpdateLayout(); //need to update before edge routing }); @@ -653,18 +643,19 @@ namespace GraphX await DispatcherHelper.CheckBeginInvokeOnUi(() => { //var size = Parent is ZoomControl ? (Parent as ZoomControl).Presenter.ContentSize : DesiredSize; - eralg.AreaRectangle = ContentSize.ToGraphX(); // new Rect(TopLeft.X, TopLeft.Y, size.Width, size.Height); + eralg.AreaRectangle = ContentSize.ToGraphX(); + // new Rect(TopLeft.X, TopLeft.Y, size.Width, size.Height); rectangles = GetVertexSizeRectangles(resultCoords, vertexSizes); }); eralg.VertexPositions = resultCoords; eralg.VertexSizes = rectangles; - eralg.Compute(); + eralg.Compute(cancellationToken); OnEdgeRoutingCalculationFinished(); if (eralg.EdgeRoutes != null) foreach (var item in eralg.EdgeRoutes) item.Key.RoutingPoints = item.Value; //if (Worker != null) Worker.ReportProgress(99, 1); - + } await DispatcherHelper.CheckBeginInvokeOnUi(() => { @@ -688,64 +679,99 @@ namespace GraphX } else OnRelayoutFinished(); }); - } - finally - { - IsCalculating = false; - } + }, cancellationToken); } /// /// Relayout graph using the same vertexes /// /// Generate all available edges for graph - public void RelayoutGraph(bool generateAllEdges = false) + public Task RelayoutGraphAsync(bool generateAllEdges = false) { - _relayoutGraphMain(generateAllEdges); + return RelayoutGraphAsync(CancellationToken.None, generateAllEdges); } - private void _relayoutGraphMain(bool generateAllEdges = false, bool standalone = true) + public Task RelayoutGraphAsync(CancellationToken cancellationToken, bool generateAllEdges = false) { - if (LogicCore == null) - throw new GX_InvalidDataException("LogicCore -> Not initialized!"); + return _relayoutGraphMainAsync(cancellationToken, generateAllEdges, standalone: true); + } - if (LogicCore.AsyncAlgorithmCompute) + private async Task _relayoutGraphMainAsync(CancellationToken externalCancellationToken, bool generateAllEdges = false, bool standalone = true) + { + await CancelRelayout(); + + _layoutCancellationSource = new CancellationTokenSource(); + + if (externalCancellationToken != CancellationToken.None) + _linkedLayoutCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_layoutCancellationSource.Token, externalCancellationToken); + + _layoutTask = _relayoutGraph((_linkedLayoutCancellationSource ?? _layoutCancellationSource).Token, generateAllEdges, standalone); + await _layoutTask; + } + + private Task _layoutTask = null; + private CancellationTokenSource _layoutCancellationSource; + private CancellationTokenSource _linkedLayoutCancellationSource; + + public async Task CancelRelayout() + { + if (_layoutTask != null) { - _relayoutGraph(generateAllEdges, standalone); - return; - } - _relayoutGraph(generateAllEdges, standalone).Wait(); - } + _layoutCancellationSource.Cancel(); + try + { + await _layoutTask; + } + catch (OperationCanceledException) + { + // This is expected, so just ignore it + } + _layoutTask = null; + _layoutCancellationSource.Dispose(); + _layoutCancellationSource = null; + + if (_linkedLayoutCancellationSource != null) + { + _linkedLayoutCancellationSource.Dispose(); + _linkedLayoutCancellationSource = null; + } + } + } #endregion /// - /// Generate visual graph + /// Generate visual graph asynchronously /// /// Data graph /// Generate all available edges for graph /// Sets visual edge and vertex controls DataContext property to vertex data item of the control (Allows prop binding in xaml templates) - public void GenerateGraph(TGraph graph, bool generateAllEdges = false, bool dataContextToDataItem = true) + public Task GenerateGraphAsync(TGraph graph, bool generateAllEdges = false, bool dataContextToDataItem = true) + { + return GenerateGraphAsync(graph, CancellationToken.None, generateAllEdges, dataContextToDataItem); + } + + public Task GenerateGraphAsync(TGraph graph, CancellationToken cancellationToken, bool generateAllEdges = false, bool dataContextToDataItem = true) { if (AutoAssignMissingDataId) AutoresolveIds(graph); - if(!LogicCore.IsCustomLayout) + if (!LogicCore.IsCustomLayout) PreloadVertexes(graph, dataContextToDataItem); - _relayoutGraphMain(generateAllEdges, false); + return _relayoutGraphMainAsync(cancellationToken, generateAllEdges, false); } /// - /// Generate visual graph using Graph property (it must be set before this method is called) + /// Generate visual graph asynchronously using Graph property (it must be set before this method is called) /// /// Generate all available edges for graph /// Sets visual edge and vertex controls DataContext property to vertex data item of the control (Allows prop binding in xaml templates) - public void GenerateGraph(bool generateAllEdges = false, bool dataContextToDataItem = true) + public Task GenerateGraphAsync(bool generateAllEdges = false, bool dataContextToDataItem = true) { if (LogicCore == null) throw new GX_InvalidDataException("LogicCore -> Not initialized! (Is NULL)"); if (LogicCore.Graph == null) throw new InvalidDataException("GraphArea.GenerateGraph() -> LogicCore.Graph property is null while trying to generate graph!"); - GenerateGraph(LogicCore.Graph, generateAllEdges, dataContextToDataItem); + return GenerateGraphAsync(LogicCore.Graph, generateAllEdges, dataContextToDataItem); } private void AutoresolveIds(TGraph graph = null) @@ -1025,7 +1051,7 @@ namespace GraphX } var orAlgo = LogicCore.AlgorithmFactory.CreateFSAA(sizes, 15f, 15f); - orAlgo.Compute(); + orAlgo.Compute(CancellationToken.None); foreach (var item in orAlgo.Rectangles) { if (item.Key.IsVertex) @@ -1047,7 +1073,7 @@ namespace GraphX { LogicCore.AlgorithmStorage.EdgeRouting.VertexSizes = GetVertexSizeRectangles(); LogicCore.AlgorithmStorage.EdgeRouting.VertexPositions = GetVertexPositions(); - LogicCore.AlgorithmStorage.EdgeRouting.Compute(); + LogicCore.AlgorithmStorage.EdgeRouting.Compute(CancellationToken.None); if (LogicCore.AlgorithmStorage.EdgeRouting.EdgeRoutes != null) foreach (var item in LogicCore.AlgorithmStorage.EdgeRouting.EdgeRoutes) item.Key.RoutingPoints = item.Value; diff --git a/GraphX.METRO.Controls/Models/DispatcherHelper.cs b/GraphX.METRO.Controls/Models/DispatcherHelper.cs index c151679..0df046f 100644 --- a/GraphX.METRO.Controls/Models/DispatcherHelper.cs +++ b/GraphX.METRO.Controls/Models/DispatcherHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Windows.UI.Core; using Windows.UI.Xaml; @@ -7,20 +8,14 @@ namespace GraphX { public static class DispatcherHelper { - public static CoreDispatcher UiDispatcher { get; private set; } - public static async Task CheckBeginInvokeOnUi(Action action) { - if (UiDispatcher.HasThreadAccess) - action(); - else await UiDispatcher.RunAsync(CoreDispatcherPriority.Normal, - () => action()); - } + var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher; - static DispatcherHelper() - { - if (UiDispatcher != null) return; - UiDispatcher = Window.Current.Dispatcher; + if (dispatcher.HasThreadAccess) + action(); + else await dispatcher.RunAsync(CoreDispatcherPriority.Normal, + () => action()); } } } From e2b8e963df341c1416471863b8724e262cb2f5b6 Mon Sep 17 00:00:00 2001 From: jorgensigvardsson Date: Mon, 13 Apr 2015 23:44:42 +0200 Subject: [PATCH 2/2] CancelRelayout -> CancelRelayoutAsync --- GraphX.METRO.Controls/Controls/GraphArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GraphX.METRO.Controls/Controls/GraphArea.cs b/GraphX.METRO.Controls/Controls/GraphArea.cs index be8b53a..f6dc4ad 100644 --- a/GraphX.METRO.Controls/Controls/GraphArea.cs +++ b/GraphX.METRO.Controls/Controls/GraphArea.cs @@ -698,7 +698,7 @@ namespace GraphX private async Task _relayoutGraphMainAsync(CancellationToken externalCancellationToken, bool generateAllEdges = false, bool standalone = true) { - await CancelRelayout(); + await CancelRelayoutAsync(); _layoutCancellationSource = new CancellationTokenSource(); @@ -713,7 +713,7 @@ namespace GraphX private CancellationTokenSource _layoutCancellationSource; private CancellationTokenSource _linkedLayoutCancellationSource; - public async Task CancelRelayout() + public async Task CancelRelayoutAsync() { if (_layoutTask != null) {