c# - C# - 是否可以将`ref bool`转换为CancellationToken?

145 5

这是我使用的内容:


void Method(ref bool isCancelled)


{


 while (!isCancelled)


 {


 ...


 DoThis();


 DoThat();


 ...


 }


}



这就是我想要做的:


Task MethodAsync(ref bool isCancelled)


{


 while (!isCancelled)


 {


 ...


 DoThis();


 await DoTheNewThingAsync(isCancelled.ToCancellationToken());


 DoThat();


 ...


 }


}



当然,ToCancellationToken()并不存在于这个上下文中,它只是用来显示意图。

我试图创建CancellationTokenSource的自定义实现,但是在这个类中没有什么东西可以使用,因为它是struct并且不能继承,所以也不能直接创建自定义CancellationToken

我知道使用ref bool是一个不好的实践,但是我目前不能更改依赖它的基础实现。

时间: 原作者:

72 2

这很复杂,因为有几个原因:

  • 不能通过ref将参数传递给async方法,你正在使用await,但是如果要使用await,你的方法需要标记为async,async方法不能有ref参数,例如,这将无法编译:
  • 
    async Task MethodAsync(ref bool isCancelled)
    
    
    {
    
    
     while (!isCancelled)
    
    
     {
    
    
     DoThis();
    
    
     await DoTheNewThingAsync(isCancelled.ToCancellationToken());
    
    
     DoThat();
    
    
     }
    
    
    }
    
    
    
    

这将给你提供编译器错误:

CS1988 : Async methods cannot have ref ,in or out parameters

  • 不能在匿名方法中使用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 : cannot use ref ,out ,or in parameter'isCancelled'inside an anonymous method ,lambda expression ,query expression ,or local function

我没有找到有其他方法以引用方式将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();
    
    
     }
    
    
    }
    
    
    
    

但是,这会阻止调用者,因此我并不完全看到这是如何起作用的(如果调用方被阻止,调用方如何更改isCancelled),

原作者:
132 4

我已经hack了一些可以工作的方案:


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();


 ...


 }


}



警告:此代码在调用线程时同步运行,因此,它不能保证它能与代码的其他部分很好地工作,因为它阻塞了调用线程,同时,它轮询isCancelled变量,使它既无效,又不立即取消。

我认为这是一个间隙解决方案,因为你用正确的任务来替代ref bool isCancelled。

原作者:
66 1

此函数使用Task.Wait重载,而不是Thread.Sleep,它接受超时,这样就不会给任务的完成造成额外的延迟。


public static void Wait(Func<CancellationToken, Task> taskFactory,


 ref bool cancel, int pollInterval = 100)


{


 using (var cts = new CancellationTokenSource())


 {


 if (cancel) cts.Cancel();


 var task = taskFactory(cts.Token);


 while (!cancel)


 {


 if (task.Wait(pollInterval)) return;


 }


 cts.Cancel();


 task.Wait();


 }


}



用法示例:


Wait(DoTheNewThingAsync, ref isCancelled);



原作者:
54 5

如果你正在制作方法async Task并且仍然想使用bool语义,则必须传递一个对象,以便引用bool值,如果bool参数可以在客户端代码中转换为Ref<bool>,则不需要阻塞操作即可执行这个操作:


public class Ref


{


 public static Ref<T> Create<T>(T value) => new Ref<T>(value);


}



public class Ref<T> : Ref


{


 public Ref(T value) { Value = value; }


 public T Value { get; set; }


 public override string ToString() => Value?.ToString() ??"";


 public static implicit operator T(Ref<T> r) => r.Value;


}



public static class RefExtensions


{


 public static CancellationToken ToCancellationToken(this Ref<bool> cancelled)


 {


 var cts = new CancellationTokenSource();


 Task.Run(async () =>


 {


 while (!cancelled) await Task.Delay(100);


 cts.Cancel();


 });


 return cts.Token;


 }


}



public async Task Method(Ref<bool> isCancelled)


{


 var cancellationToken = isCancelled.ToCancellationToken();



 while(!isCancelled)


 {


 ...


 DoThis();


 await DoTheNewThingAsync(cancellationToken);


 DoThat();


 ...


 }


}



public class Tests


{


 [Fact]


 public async Task Fact()


 {


 var cancelled = Ref.Create(false);



 Task.Run(async () =>


 {


 await Task.Delay(500);


 cancelled.Value = true;


 });



 var task = Method(cancelled);


 await Task.Delay(1000);



 task.Status.Should().Be(TaskStatus.RanToCompletion);


 }


}



原作者:
...