【问题标题】:Task cancel not stopping task execution任务取消不停止任务执行
【发布时间】:2016-07-06 15:17:56
【问题描述】:

我有一个Task,它查询一个活动目录并用结果填充一个列表。我已经设置了我的任务,以便可以取消它,但是,当调用取消时,任务会继续执行它的工作。我知道任务已被取消,因为它返回并且打算在任务返回时执行的操作正在运行,但查询继续在后台运行,使用内存和处理能力。任务可以重复启动和“取消”,任务的每次迭代都在运行和使用资源。如何使取消实际取消?

视图模型

private async Task RunQuery(QueryType queryType,
    string selectedItemDistinguishedName = null)
{
    StartTask();
    try
    {
        _activeDirectoryQuery = new ActiveDirectoryQuery(queryType,
            CurrentScope, selectedItemDistinguishedName);
        await _activeDirectoryQuery.Execute();
        Data = _activeDirectoryQuery.Data.ToDataTable().AsDataView();
        CurrentQueryType = queryType;
    }
    catch (ArgumentNullException)
    {
        ShowMessage(
            "No results of desired type found in selected context.");
    }
    catch (OutOfMemoryException)
    {
        ShowMessage("The selected query is too large to run.");
    }
    FinishTask();
}

private void CancelCommandExecute()
{
    _activeDirectoryQuery?.Cancel();
}

ActiveDirectoryQuery

public async Task Execute()
{
    _cancellationTokenSource = new CancellationTokenSource();
    var taskCompletionSource = new TaskCompletionSource<object>();
    _cancellationTokenSource.Token.Register(
        () => taskCompletionSource.TrySetCanceled());
    DataPreparer dataPreparer = null;
    var task = Task.Run(() =>
    {
        if (QueryTypeIsOu())
        {
            dataPreparer = SetUpOuDataPreparer();
        }
        else if (QueryTypeIsContextComputer())
        {
            dataPreparer = SetUpComputerDataPreparer();
        }
        else if (QueryTypeIsContextDirectReportOrUser())
        {
            dataPreparer = SetUpDirectReportOrUserDataPreparer();
        }
        else if (QueryTypeIsContextGroup())
        {
            dataPreparer = SetUpGroupDataPreparer();
        }
        Data = GetData(dataPreparer);
    },
    _cancellationTokenSource.Token);
    await Task.WhenAny(task, taskCompletionSource.Task);
}

public void Cancel()
{
    _cancellationTokenSource?.Cancel();
}

Cancel() 由绑定到ButtonCommand 调用。该任务可能需要几分钟才能执行,并且可能会消耗数百兆字节的 RAM。如果有帮助,我可以提供任何引用的方法或任何其他信息。

【问题讨论】:

  • 您需要在任务中查询token的IsCancellationRequested属性,并相应地从任务中返回。 Cancel 不只是“杀死”任务,它只是将该属性的值设置为 true。
  • @KDecker 我假设你的意思是我需要重复查询它?考虑到实际工作做了几个更深的方法,这是不切实际的,但我的设计可能是那里的真正问题。
  • @MichaelBrandonMorris 是的,您需要对其进行测试。也可以使用ThrowIfCancellationRequested() 也可以查看How to: Cancel a Task and Its Children
  • @ConradFrix 因此,任务调用方法(见上文),每个方法调用一个静态方法,该方法可能调用其他静态方法。无论如何,首当其冲的工作发生在最深层,最深层需要返回一个对象。如何在允许返回的对象向上传播的同时沿树传播取消?
  • @MichaelBrandonMorris 取消标记与其他任何变量一样。使变量可用于静态方法的方法是相同的。通常这意味着将令牌传递给每个方法。

标签: c# task


【解决方案1】:

Cancellation is cooperative,如果要取消操作,您需要编辑要取消的函数。所以 Execute 会变成

public async Task Execute()
{
    _cancellationTokenSource = new CancellationTokenSource();
    var taskCompletionSource = new TaskCompletionSource<object>();

    //Token registrations need to be disposed when done.
    using(_cancellationTokenSource.Token.Register(
        () => taskCompletionSource.TrySetCanceled()))
    {
        DataPreparer dataPreparer = null;
        var task = Task.Run(() =>
        {
            if (QueryTypeIsOu())
            {
                dataPreparer = SetUpOuDataPreparer(_cancellationTokenSource.Token);
            }
            else if (QueryTypeIsContextComputer())
            {
                dataPreparer = SetUpComputerDataPreparer(_cancellationTokenSource.Token);
            }
            else if (QueryTypeIsContextDirectReportOrUser())
            {
                dataPreparer = SetUpDirectReportOrUserDataPreparer(_cancellationTokenSource.Token);
            }
            else if (QueryTypeIsContextGroup())
            {
                dataPreparer = SetUpGroupDataPreparer(_cancellationTokenSource.Token);
            }
            Data = GetData(dataPreparer, _cancellationTokenSource.Token);
        },
        _cancellationTokenSource.Token);
        await Task.WhenAny(task, taskCompletionSource.Task);
   }
}

然后从这些方法内部。如果这些函数中有循环,则需要从循环内部调用token.ThrowIfCancellationRequested()。如果您不循环并且调用了一些外部 API,则需要使用该 API 的取消方法,希望 API 将接受 CancellationToken,如果不接受并且您需要调用 .Cancel() 方法,请使用 @987654328 @ 方法,就像你在 Execute 中所做的那样。

如果 API 没有提供取消查询的方法,那么提前停止查询的唯一安全方法是您需要将查询移至单独的 exe。当您执行查询时,您会执行 var proc = Process.Start(...) 来启动 exe。要与它通信,请使用某种形式的 IPC,例如 WCF over Named Pipes,您可以在进程启动之前生成 Guid,并将其作为参数传入,然后使用该 guid 作为命名管道的名称。如果您需要提前结束查询,请执行 proc.Kill() 以结束外部进程。

【讨论】:

  • 谢谢。代码是我自己的,所以我检查并修改了其中的每个方法以接受CancellationToken
  • 我就是这样做的,我认为这是唯一的方法。必须查询属性/传递令牌似乎很混乱。我一直想问是否有更清洁的方法,但我怀疑它是如何工作的。 // 这个方法也很好,因为你可以在计算中“深度”取消,而不是等待长时间运行的方法完成并在“任务级别”取消。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-28
  • 1970-01-01
相关资源
最近更新 更多