Reputation: 1073
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?
Upvotes: 3
Views: 1086
Reputation: 169200
The CollectPoints
method will return whenever it hits the while
condition !token.IsCancellationRequested
the first time after you have called the Cancel()
method of the CancellationTokenSource
.
The task won't be cancelled while the code inside the while
loop is still executing.
As @JSteward suggests in his comment, you should cancel or complete the TaskCompletionSource
in your StopSegment()
method.
Something like this:
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)
{
try
{
// wait for a new point.
var point = await CollectPoint(token);
//...
}
catch (Exception) { }
}
}
private TaskCompletionSource<MapPoint> tcs;
protected override Task<MapPoint> CollectPoint()
{
tcs = new TaskCompletionSource<MapPoint>();
//...
return tcs.Task;
}
public void StopSegment()
{
// interrupt the CollectPoints task.
_cancellationToken.Cancel();
tcs.SetCanceled();
}
Upvotes: 4