Use a CancellationToken to interrupt nested tasks

Viewed 759

Here is my scenario: The user clicks a WPF button which initiates an open-ended period for the collection of points on a map. When the user clicks the "finished collecting" button, I want the CollectPoints() task to complete.

Here are pieces of my SegmentRecorder class:

    private CancellationTokenSource _cancellationToken;     

    public virtual async void RecordSegment(IRoadSegment segment)
    {
        _cancellationToken = new CancellationTokenSource();
        var token = _cancellationToken.Token;

        // await user clicking points on map
        await CollectPoints(token);

        // update the current segment with the collected shape.
        CurrentSegment.Shape = CurrentGeometry as Polyline;
    }

    // collect an arbitrary number of points and build a polyline.
    private async Task CollectPoints(CancellationToken token)
    {
        var points = new List<MapPoint>();
        while (!token.IsCancellationRequested)
        {
            // wait for a new point.
            var point = await CollectPoint();
            points.Add(point);

            // add point to current polyline
            var polylineBuilder = new PolylineBuilder(points, SpatialReferences.Wgs84);
            CurrentGeometry = polylineBuilder.ToGeometry();

            // draw points
            MapService.DrawPoints(CurrentGeometry);
        }
    }

    // collect a point from map click.
    protected override Task<MapPoint> CollectPoint()
    {
        var tcs = new TaskCompletionSource<MapPoint>();
        EventHandler<IMapClickedEventArgs> handler = null;
        handler = (s, e) =>
        {
            var mapPoint = e.Geometry as MapPoint;
            if (mapPoint != null)
            {
                tcs.SetResult(new MapPoint(mapPoint.X, mapPoint.Y, SpatialReferences.Wgs84));
            }
            MapService.OnMapClicked -= handler;
        };
        MapService.OnMapClicked += handler;

        return tcs.Task;
    }

    public void StopSegment(){
        // interrupt the CollectPoints task.
        _cancellationToken.Cancel();
    }

Here are the relevant parts of my view model:

public SegmentRecorder SegmentRecorder { get; }
public RelayCommand StopSegment { get; }

public ViewModel(){
    StopSegment = new RelayCommand(ExecuteStopSegment);
    SegmentRecorder = new SegmentRecorder();
}

// execute on cancel button click.
public void ExecuteStopSegment(){
    SegmentRecorder.StopSegment();
}

When I put a breakpoint on the line while (!token.IsCancellationRequested) and click the cancel button, I never get to that point.

Am I using the cancellation token in the correct way here?

1 Answers
Related