使用CancellationToken中断嵌套任务

3
这是我的场景:用户单击 WPF 按钮,启动地图上的点采集开放周期。当用户单击“完成采集”按钮时,我希望 CollectPoints() 任务完成。
这是我的 SegmentRecorder 类的一些部分:
    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();
    }

以下是我的视图模型的相关部分:
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();
}

当我在while (!token.IsCancellationRequested)这一行设置断点并点击取消按钮时,我从来没有到达过那个点。

我在这里使用取消令牌的方式正确吗?


2
一个小提示:对于不是事件处理程序的方法,您不应该使用async void - dymanoid
你需要在CollectPoint中处理取消操作,这可能是它当前等待的地方。 - JSteward
2
一旦用户停止收集积分,他们需要再收集至少一个积分才能完成“CollectPoint”任务,该任务会暂停您的“while”的执行。将“tcs”保持在“CollectPoint”之外,并在用户想要停止收集时完成或取消它,然后您的while将按预期完成。 - JSteward
1个回答

4
CollectPoints 方法在您调用 CancellationTokenSourceCancel() 方法后,第一次触发 while 条件 !token.IsCancellationRequested 后返回。
只要 while 循环内的代码仍在执行,任务就不会被取消。
如 @JSteward 在他的评论中建议的那样,在 StopSegment() 方法中应该取消或完成 TaskCompletionSource
像这样:
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();
}

我的解决方案与这个非常相似,只是我还创建了一个新的Task并将我的令牌传递给CollectPoint,如下所示:var point = await Task.Run(CollectPoint(token), token); - wdonahoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接