是否可以将`ref bool`转换为CancellationToken?

问题描述 投票:1回答:3

我有一个遗留场景,其中ref bool被用来向实现发送取消信号。现在,我想调用一个采用Task实例的基于CancellationToken的库方法,当布尔值更改值时,我也想取消该实例。

这是我必须使用的:

void Method(ref bool isCancelled)
{
    while (!isCancelled)
    {
        ...
        DoThis();
        DoThat();
        ...
    }
}

这就是我想做的:

Task MethodAsync(ref bool isCancelled)
{
    while (!isCancelled)
    {
        ...
        DoThis();
        await DoTheNewThingAsync(isCancelled.ToCancellationToken());
        DoThat();
        ...
    }
}

ToCancellationToken()当然在此上下文中不存在,仅用于表示意图。

我试图创建CancellationTokenSource的自定义实现,但是该类中没有虚拟的东西可以使用。也无法直接创建自定义CancellationToken,因为它是struct并且无法继承。

我知道使用ref bool是一种不好的做法,但是我目前无法更改依赖它的基础实现,因此我需要一种方法来将其值用作基于任务的调用的取消机制。

c# .net async-await task-parallel-library cancellation
3个回答
1
投票

很复杂。由于某些原因:

  1. 您无法将ref的参数传递给async方法。您正在使用await,但是要使用await,您的方法需要标记为async。并且async方法不能具有ref参数。例如,它将不会编译:
async Task MethodAsync(ref bool isCancelled)
{
    while (!isCancelled)
    {
        DoThis();
        await DoTheNewThingAsync(isCancelled.ToCancellationToken());
        DoThat();
    }
}

会给您编译器错误:

CS1988:异步方法不能具有ref,in或out参数

  1. 您不能在匿名方法中使用ref参数。我考虑过使用Timer检查变量。像这样的东西:
public static CancellationToken ToCancellationToken(ref bool isCancelled)
{
    var tokenSource = new CancellationTokenSource();

    var timer = new System.Timers.Timer()
    {
        AutoReset = true,
        Interval = 100
    };
    timer.Elapsed += (source, e) =>
    {
        if (isCancelled)
        {
            tokenSource.Cancel();
            timer.Dispose();
        }
    };
    timer.Enabled = true;

    return tokenSource.Token;
}

但是这会给您带来编译器错误:

CS1628:在匿名方法,lambda表达式,查询表达式或局部函数内不能使用ref,out或in参数'isCancelled'

我没有看到其他通过引用将bool放入事件处理程序的方法。

  1. 我能得到的最接近的东西是这样的:
void Method(ref bool isCancelled)
{
    var tokenSource = new CancellationTokenSource();
    while (!isCancelled)
    {
        DoThis();
        var mytask = DoTheNewThingAsync(tokenSource.Token);
        while (true)
        {
            //wait for either the task to finish, or 100ms
            if (Task.WaitAny(mytask, Task.Delay(100)) == 0)
            {
                break; //mytask finished
            }
            if (isCancelled) tokenSource.Cancel();
        }
        DoThat();
    }
}

但是,这阻止了呼叫者,所以我不完全知道那怎么可能有用。但这就是您现有方法正在做的事情,所以也许可行?


0
投票

我已经破解了一个可行的解决方案:

public static class TaskRefBoolCancellable
{
    public static T SynchronousAwait<T>(Func<CancellationToken, Task<T>> taskToRun, ref bool isCancelled)
    {
        using (var cts = new CancellationTokenSource())
        {
            var runningTask = taskToRun(cts.Token);

            while (!runningTask.IsCompleted)
            {
                if (isCancelled)
                    cts.Cancel();

                Thread.Sleep(100);
            }

            return runningTask.Result;
        }
    }
}

void Method(ref bool isCancelled)
{
    while (!isCancelled)
    {
        ...
        DoThis();
        var result = TaskRefBoolCancellable.SynchronousAwait(DoTheNewThingAsync, ref isCancelled);
        DoThat();
        ...
    }
}

WARNING:此代码在调用线程上同步运行。因此,不能保证它会与代码的其他部分很好地配合,因为它会阻塞调用线程。另外,它轮询isCancelled变量,使其无效并且取消不是立即进行的。

我认为这是一个权宜之计,因为您将ref bool isCancelled替换为基于任务的适当取消。

© www.soinside.com 2019 - 2024. All rights reserved.