jul*_*gon 5 .net c# task-parallel-library cancellation async-await
我有一个遗留场景,其中ref bool有人使用来向实现发送取消信号。现在,我想调用一个Task基于CancellationToken实例的基于库的方法,该方法也要在布尔值更改时被取消。
这是我必须使用的:
void Method(ref bool isCancelled)
{
    while (!isCancelled)
    {
        ...
        DoThis();
        DoThat();
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)
这就是我想要做的:
Task MethodAsync(ref bool isCancelled)
{
    while (!isCancelled)
    {
        ...
        DoThis();
        await DoTheNewThingAsync(isCancelled.ToCancellationToken());
        DoThat();
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)
ToCancellationToken() 在这种情况下当然不存在,仅用于显示意图。
我试图创建一个自定义实现,CancellationTokenSource但是该类中没有虚函数可以使用。也不能CancellationToken直接创建自定义,因为它是一个struct并且不能继承。
我知道使用a ref bool是一种不好的做法,但是我目前无法更改依赖它的基础实现,因此我需要一种方法来将其值用作基于任务的调用的取消机制。
情况很复杂。有几个原因:
ref给async方法。您正在使用await,但要使用await,需要标记您的方法async。和async方法不能有ref参数。例如,它将不会编译:async Task MethodAsync(ref bool isCancelled)
{
    while (!isCancelled)
    {
        DoThis();
        await DoTheNewThingAsync(isCancelled.ToCancellationToken());
        DoThat();
    }
}
Run Code Online (Sandbox Code Playgroud)
那会给你编译器错误:
CS1988:异步方法不能具有ref,in或out参数
ref在匿名方法中使用参数。我考虑过使用a 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;
}
Run Code Online (Sandbox Code Playgroud)
但这给了您编译器错误:
CS1628:无法在匿名方法,lambda表达式,查询表达式或局部函数内使用ref,out或in参数'isCancelled'
我看不到任何其他bool通过引用进入事件处理程序的方法。
void Method(ref bool isCancelled)
{
    while (!isCancelled)
    {
        DoThis();
        using (var tokenSource = new CancellationTokenSource()) {
            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();
            }
            // This will throw an exception if an exception happened in
            // DoTheNewThingAsync. Otherwise we'd never know if it
            // completed successfully or not.
            mytask.GetAwaiter().GetResult();
        }
        DoThat();
    }
}
Run Code Online (Sandbox Code Playgroud)
但是,这阻塞了调用者,因此我什至没有完全看到它甚至可能是有用的(isCancelled如果被阻塞,调用者如何改变?)。但这就是您现有方法正在做的事情,所以也许行得通吗?
但这是超级hacky。如果您完全可以控制上游的操作方式,请改为执行此操作。
我已经破解了一个有点有效的解决方案:
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();
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)
警告:此代码在调用线程上同步运行。因此,不能保证它能够与代码的其他部分很好地配合,因为它会阻塞调用线程。此外,它还会轮询isCancelled变量,使其无效并且不会立即取消。
ref bool isCancelled当您用适当的基于任务的取消替换 时,我认为这是一个权宜之计的解决方案。