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


 }


}



原作者:
...