任务取消不停止任务执行

本文关键字:任务 执行 取消 不停止 | 更新日期: 2023-09-27 17:55:14

我有一个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();
}

活动目录查询

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。如果有帮助,我可以提供任何引用的方法或任何其他信息。

任务取消不停止任务执行

取消是合作的,如果你想取消操作,你需要编辑你的函数来取消。所以执行会变成

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()方法,使用Register方法,就像你在Execute中所做的那样。

如果 API 未公开取消查询的方法,则提前停止查询的唯一安全方法是需要将查询移动到单独的 exe。执行查询时,执行var proc = Process.Start(...)以启动 exe。若要与它通信,请使用某种形式的 IPC(如命名管道上的 WCF),可以在进程开始之前生成Guid并将其作为参数传入,然后使用该 guid 作为命名管道的名称。如果需要提前结束查询,请执行proc.Kill()以结束外部进程。