Timing out a task using await in .NET 4.5 - vb.net

I'm trying out Async / Await in VB.NET 4.5 and would like my task
to timeout if it doesn't complete within a certain period. I have
Await Task.Run( Sub() PerformSomeAction() )
which seems neat. I also see there is a form of Task.Run that takes a cancellation token. How could I use this to cancel the task in case of some timeout?
EDIT
I have the following prototype solution
Dim cts = New CancellationTokenSource()
Dim ct As CancellationToken = cts.Token
Dim delay = Task.Delay(1000)
Dim completed = Await Task.WhenAny(Task.Run(Sub() PerfomSomeAction(ct), ct), delay)
If completed Is delay Then
cts.Cancel()
End If
This looks quite noisy code. Is this good? Another idea would be to handle
the timeout as an exception and use cts.CancelAfter. Something like this??
Dim cts = New CancellationTokenSource()
Dim ct As CancellationToken = cts.Token
try
cts.CancelAfter(1000) 'ms
Dim completed = Task.Run(Sub() PerformSomeAction(ct), ct)
catch SomeTimeoutException
end try
and withing PerformSomeAction I throw SomeTimeoutException if I get the
detect the cancelation token.

Tasks support cooperative cancellation, that means if you want to cancel a Task that's already running, the code inside the Task has to support cancellation and it decides when will it be canceled. The way to achieve this is to pass the cancellation token to the method and call ThrowIfCancellationRequested() at suitable places in the code of that method.
If the code in the Task doesn't support cancellation, there is no clean way to cancel or abort it.

Related

VB.Net CancellationToken does not cancel the Task

I do SAP Gui Scripting in VB.Net with .Net Framework 4.8. At some point the SAP will be unresponsive due to circumstances out of my control, and the called function will completely block further execution forever. In this case i want to safely exit the code.
In order to overcome this obstacle where the SAP function call completely blocks execution my approach is to create a New System.Threading.Task, execute the blocking function in it and after a given timeout cancel the operation. I'd also like to be informed if the element had been found.
For this reason i created the following code mostly by reading docs.mycrosoft.com
Dim propablyBlockingFn = Function ()
Dim found = False
While not found
'code that interacts with sap and will either set found to true or completly block execution
End While
Return found
End Function
Dim timeout = 5000 'ms
Dim t As Task(Of Boolean) = Task.Run(fn)
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Dim taskRef = t.Wait(timeout, token)
If Not taskRef Then
cts.Cancel()
End If
Dim exists = t.Result 'it will stuck here
t.Dispose()
However, at the point where i try to read the Result of the function, the code wont execute any further and the cancel call does not have any effect.
Does anyone have an idea?
I've found a last resort solution after reading the following answer. Keep in mind that sideeffects may happen. But they would be even worse if in this case i won't abort the Thread.
Dim taskThread As Thread = Nothing
Dim propablyBlockingFn = Function ()
taskThread = Thread.CurrentThread 'Get the Thread the Task is running in
Dim found = False
While not found
'code that interacts with sap and will either set found to true or completly block execution
End While
Return found
End Function
Dim timeout = 5000 'ms
Dim t As Task(Of Boolean) = Task.Run(fn)
Dim taskRef = t.Wait(timeout)
If Not taskRef Then
taskThread.Abort() 'Abort the Thread
End If
Dim exists = t.Result

Async/ConfigureAwait(True) does not wait - WebService

I have a webservice GetCard which calls a function CreateRaces. Inside the CreateRaces I am saving the new objects. This works fine without async. However, when I call the async function for task1 CommitAsync the method executes and returns a response without hitting the next break point. I have tried to work around with via Task.Run, ConfigureWait(true), and with
With HttpContext.Current
bulk.Setup etc...
End With
but nothing seems to work.
The bulk.Setup - CommitAsync calls a third party dll (SqlBulkTools) which returns Task.
It looks like it works on a different thread.
Any ideas how to configure it so I the function executes and I can hit the next break point which is sw.Stop()?
<HttpGet>
<AllowAnonymous>
Public Async Function GetCard(cardurl As String) As HttpResponseMessage
Try
Dim oRace As New RaceCard(cardurl)
oRace.DoWork()
Dim oRC As New RaceController
await oRC.CreateRaces(oRace.Races)
Return Request.CreateResponse(HttpStatusCode.OK, True.ToJson)
Catch ex As Exception
Return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex)
End Try
End Function
CreateRaces function
Public Async Function CreateRaces(ByVal t As List(Of Race)) As Threading.Tasks.Task(Of Boolean)
Try
'Proceed only if list has items
If t.Count > 0 Then
Dim bulk As New BulkOperations()
Dim sw As New Stopwatch()
Dim oCon As New SqlClient.SqlConnection(DotNetNuke.Common.Utilities.Config.GetConnectionString)
sw.Start()
Dim task1 = Await bulk.Setup(Of Race).ForCollection(t).WithTable("ArbiRace").AddAllColumns().BulkInsertOrUpdate().SetIdentityColumn(Function(i) i.RaceID).MatchTargetOn(Function(m) m.RaceName).CommitAsync(oCon).ConfigureAwait(True)
sw.Stop()
Dim swt = sw.ElapsedMilliseconds
End If
Return True
Catch ex As Exception
Dim s As String
s = ex.ToString
Return False
End Try
End Function
Asnyc calls can be handled in 2 ways:
Deliberately halting thread execution until one, several, or all tasks are completed.
Leaving the task to run while the current thread goes to do something else.
Based on your question, you are expecting #1, but your code currently does #2. Await Documentation:
An Await expression or statement does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method, after the Await expression, as a continuation on the awaited task. Control then returns to the caller of the async method. When the task completes, it invokes its continuation, and execution of the async method resumes where it left off.
In your case, once CreateRaces() reaches the Dim task1 line (which will eventually store the result, not the task), control returns to GetCard(). However, the CreateRaces() call here also uses an Await, so control attempts to go back up the call stack again. Except, there is nowhere left to go up, because you are at the original endpoint. This is why you seem to get a response too soon.
Since you are waiting for the task to finish to get the elapsed time, you want to wait for the tasks to finish before returning a response. This can be accomplished with task.Result, task.Wait(), or the shared method Task.WaitAll().
Example:
<HttpGet>
<AllowAnonymous>
Public Function GetCard(ByVal cardUrl As String) As HttpResponseMessage
Try
Dim oRace As New RaceCard(cardurl)
oRace.DoWork()
Dim oRC As New RaceController
Dim createRacesTask As Threading.Tasks.Task(Of Boolean) = oRC.CreateRaces(oRace.Races)
'Do other tasks here while we wait
Threading.Tasks.Task.WaitAll(createRacesTask, ...)
Return Request.CreateResponse(HttpStatusCode.OK, True.ToJson)
Catch ex As Exception
Return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex)
End Try
End Function

VB.Net Task.WaitAll with Timeout Cancellation Request

I know there are a few threads and atricles about this arouynd but I just can't seem to get it to work.
Basically I have two (or more) tasks that I want to run but with a timeout and after this timeout I want all tasks to cancel and have a way to know that we've timed out so I can return this to my web page.
Here's what I have:
Dim cts As System.Threading.CancellationTokenSource = New System.Threading.CancellationTokenSource
Dim oTask1 As Task(Of SearchReturn) = Task(Of SearchReturn).Factory.StartNew(
Function()
While Not cts.IsCancellationRequested
cts.Token.ThrowIfCancellationRequested()
Return Test1(oTest)
End While
End Function, cts.Token)
Dim oTask2 As Task(Of SearchReturn) = Task(Of SearchReturn).Factory.StartNew(
Function()
While Not cts.IsCancellationRequested
cts.Token.ThrowIfCancellationRequested()
Return Test2(oTest)
End While
End Function, cts.Token)
If Not Task.WaitAll({oTask1, oTask2}, 4000, cts.Token) Then cts.Cancel()
cts.Cancel() does get called so it knows it's taken too long but it's not actually cancelling it, it just carries on until the tasks have finished.
I'm sure it's probably something simple I'm doing wrong but I jsut can't spot it.
Thanks in advance.
Andy

ContinueWith after PostAsJsonAsyc

I've got a vs2010, 4.0 vb.net, WinForms app calling AttemptLogin on a form load event.
I want to avoid blocking the form load if possible and I was hoping the tasks and continuation stuff in 4.0 would be the right way to go as I could run the main task on the default scheduler and the continuation on fromcurrentsynchronisationcontext but although I've got the OnAttemptLogin working I can't get my OnAttemptLoginCompleted function in the continuation to be called.
I think it's because the OnAttemptLogin returns a "RunToCompletion" task so the continuation never gets called. But I don't know how to deal with that, I've tried numerous things but I've now confused myself so much I'm pretty much mashing keys. Can anyone offer any advice? Am I simply doing it wrong or have I got the wrong idea all together?
Here's what I have so far, the OnAttemptLogin works as I would expect it to, but it then never calls the LongRunning tasks continuation.
Please note: I can't use await as I'm in vs2010 .net4.0 so I'm stuck with ContinueWith.
Public Sub AttemptLogin(OnAttemptLoginCompleted As Action(Of Task(Of HttpResponseMessage)))
Try
Dim LongRunningTask As Task(Of HttpResponseMessage) = Task.Factory.StartNew(Function()
Return OnAttemptLogin()
End Function, TaskScheduler.Default)
Dim UITask As Task(Of HttpResponseMessage) = LongRunningTask.ContinueWith(Sub(t)
OnAttemptLoginCompleted(t)
End Sub, TaskScheduler.FromCurrentSynchronizationContext)
LongRunningTask.Wait()
Catch ex As AggregateException
' nom nom nom
' do something useful
End Try
End Sub
Private Function OnAttemptLogin() As Task(Of HttpResponseMessage)
Dim aClient = New HttpClient()
Using (aClient)
' CREATE REQUEST
aClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
aClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(String.Format("{0}:{1}", CallingDTO.Email, CallingDTO.Password))))
UserQueryDTO.UserName = UserDTO.Email
UserQueryDTO.Password = UserDTO.Password
Dim url As String = DnnRequest.GetUrl(Credentials.HttpAlias, cstModuleAssembly, "User", "CanLogin", False)
' POST REQUEST
Dim p As Task(Of HttpResponseMessage) = aClient.PostAsJsonAsync(url, UserQueryDTO).ContinueWith(Function(x)
' READ RESPONSE
Dim r = x.Result.Content.ReadAsAsync(Of HttpResponseMessage)()
r.Wait()
Return r.Result
End Function)
Try
p.Wait()
Catch ex As Exception
End Try
Return p
End Using
End Function
The problem here is ... convoluted. The main issue you have here, the reason why UITask won't run, is because LongRunningTask is not of type Task(Of HttpResponseMessage). It is actually a Task(Of Task(Of HttpResponseMessage)). OnAttempLogin() returns a Task(of H...), but the task that you start in form load is a Task that will return that Task, hence, Task(Of Task(Of ...)). So there's an exception in that line, hence the UITask line never runs. So the problem with the code is that there's too many Task things all over the place.
The other problem is that you aren't really doing anything asynchronously (except that part that never ran) since you are Wait()-ing for all the tasks. So you need to get rid of most of your waits to actually achieve that. Getting rid of the waits means you need to handle exceptions with a continuation.
Some minor points:
You don't really need the scheduler stuff, either.
UITask is simply a Task, not a Task(Of ...) since it doesn't return anything.
I'm continuing from UITask to handle exceptions so that it also catches UITask's exceptions. If I continued from LongRunningTask, I would miss those exceptions.
Below is an example of what I think the new code will look like. There may be a few syntax issues since I'm missing a few things to get this to compile:
Public Sub AttemptLogin(OnAttemptLoginCompleted As Action(Of Task(Of HttpResponseMessage)))
Dim LongRunningTask As Task(Of HttpResponseMessage) = OnAttemptLogin()
Dim UITask As Task = LongRunningTask.ContinueWith(AddressOf OnAttemptLoginCompleted)
uiTask.ContinueWith(Sub(t)
Dim ex As AggregateException = t.Exception
'nom nom nom
'all your exceptions will end up here.
End Sub, TaskContinuationOptions.OnlyOnFaulted)
End Sub
Private Function OnAttemptLogin() As Task(Of HttpResponseMessage)
Dim aClient = New HttpClient()
Using (aClient)
' CREATE REQUEST
aClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
aClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(String.Format("{0}:{1}", CallingDTO.Email, CallingDTO.Password))))
UserQueryDTO.UserName = UserDTO.Email
UserQueryDTO.Password = UserDTO.Password
Dim url As String = DnnRequest.GetUrl(Credentials.HttpAlias, cstModuleAssembly, "User", "CanLogin", False)
' POST REQUEST
Dim p As Task(Of HttpResponseMessage) = aClient.PostAsJsonAsync(url, UserQueryDTO).ContinueWith(Function(x)
' READ RESPONSE
Dim r = x.Result.Content.ReadAsAsync(Of HttpResponseMessage)()
r.Wait()
Return r.Result
End Function)
Try
p.Wait()
Catch ex As Exception
End Try
Return p
End Using
End Function
my solution was to delete everything and give up, i will use something else, anything else, pff at this point ill lock the ui and not care, three days on this rubbish is crazy.
Marking jtseng's reply as correct even though it didnt work as hes the only reply and deserves something for taking the time to try and help.

How do I stop/cancel a task processing a function or find a better way of doing this?

I have ...
Private Sub TestTask()
Debug.Write("Running")
For i As Integer = 0 To 60
Debug.Write(".")
System.Threading.Thread.Sleep(1000)
Next
Debug.WriteLine("Finished")
End Sub
....
Dim cts As New CancellationTokenSource
Dim oToken As CancellationToken = cts.Token
'Create HelperTask to wait for cancellation request
Dim oHelperTask As Task = Task.Factory.StartNew(Function()
'Create Task to invoke function
Dim oTask As Task = Task.Factory.StartNew(Function()
Return outerFunction.Invoke
End Function, oToken)
' wait for cancellation token if Task is not complete
While oTask.Status = TaskStatus.Running
Thread.Sleep(200)
If oToken.IsCancellationRequested Then
oToken.ThrowIfCancellationRequested()
Return Nothing
End If
End While
Return oTask.Result
End Function, oToken)
cts.cancel()
But in my debug window on visual sudio my TestTask() continues to run with ..... please anyone enlighten me. Thanks
The whole point of the CancellationToken is that the actual worker lambda (or function) should check it to see if it should stop. In your case, TestTask must have access to the token and check it after each iteration. Neither the multiple helper tasks or the checks for the task status or the cancellation request check are necessary.
The MSDN article on Task Cancelation shows how the only thing required is for the lambda to check the token, nothing more.
In your case, TestTask can respond to a cancellation with code as simple as this:
Sub Main()
Dim cts As New CancellationTokenSource
Dim token = cts.Token
Task.Factory.StartNew(Sub() TestTask(token), token)
Thread.Sleep(3000)
cts.Cancel()
Console.ReadKey()
End Sub
Private Sub TestTask(token As CancellationToken)
Console.Write("Running")
For i As Integer = 0 To 60
token.ThrowIfCancellationRequested()
Console.Write(".")
Thread.Sleep(1000)
Next
Console.WriteLine("Finished")
End Sub
The only thing needed is to pass the token to TestTask and start it like this:
Task.Factory.StartNew(Sub() TestTask(token), token)
You don't want/need 2 tasks - it's cooperative cancellation, so every task you want to end when cancel is called will need to include ThrowIfCancellationRequested (or however it should handle cancellation). There's intentionally no Thread.Abort type behavior/semantics, it's all cooperative.