How do I deal without ByRef in an Async function?

2019-08-02 05:07发布

问题:

I'm working on replacing my old code that uses threads to a .NET 4.5 Task based system.

I have the following sub that I wish to convert;

Public Function Delay(Milliseconds As Integer, ByRef Job As Job) As Boolean
   For i As Integer = 1 To CInt(Milliseconds / 100)
      Thread.Sleep(100)
      If Job.IsCancelled Then Return True
   Next
   Return False
End Function

The Job class is one of my own, but importantly it contains an integer which is set to 1 if the job should be cancelled. So, the Delay function allows me to put a thread to sleep for X milliseconds, but if the job is cancelled during that time it will stop sleeping and return True.

I'm not sure how to do this with Await Task.Delay, here's my attempt;

  Public Async Function Delay(Milliseconds As Integer, Job As Job) As Tasks.Task(Of Boolean)
    For i As Integer = 1 To CInt(Milliseconds / 100)
      Await Tasks.Task.Delay(100)
      If Job.IsCancelled Then Return True
    Next
    Return False
  End Function

The problem I'm having is that Async methods don't allow me to use ByRef, which means it won't be able to detect when the Job has been cancelled. Another issue is that await can't be used inside try/catch/finally or a synclock. I was under the impression you can write asynchronous code that looks like synchronous code. How can I get around these issues?

回答1:

You're not actually mutating the Job variable in the body of your method, so you shouldn't be passing it by reference at all, you should be passing it by value.

That is, unless Job is actually a mutable value type instead of a reference type, in which case that is your problem and you should fix it by not using a mutable value type here.

It's also worth noting that if you wish to use an object to encapsulate the possible cancellation of an asynchronous operation you should use CancellationToken (and the accompanying CancellationTokenSource). It will provide all of the functionality that you need, and of particular note, it will handle all of the proper synchronization such that you won't run into any problems accessing it from multiple threads. It also happens to be a reference type.

If you accept a CancellationToken you'll also be able to pass it to Task.Delay, allowing it to complete when the token is cancelled, rather than forcing your method to wait until the delay is over to return false.



回答2:

Make sure to declare Job as a Class, and not a Structure. This will allow it to be passed ByVal (the default) and work perfectly in your code as described, as it will be a reference type and mutations handled externally will be visible within your method.

Since you don't change Job inside of your function, it shouldn't be passed ByRef.