Cancellation doesn't go as expected - c#

The while loop currently blocks the thread as it retries forever until the connection is established. I expect it to retry forever but it shouldn't block the thread (just like if we call StartAsync without awaiting it) and make it possible for us to call StopAsync during StartAsync's execution and cancel that process of retrying forever. It would require passing a CancellationToken to ConnectAsync too.
await client.StartAsync(); // do not block
await Task.Delay(5000);
await client.StopAsync();
I was thinking of moving the CancellationToken before the while loop and pass it to the while loop as well as loop until while (!await ConnectAsync().ConfigureAwait(false) && !_tokenSource.IsCancellationRequested) and then wrap that logic into a Task.Run to prevent blocking. What do you think?
Full code (#GeneralClient2 class)
public async Task StartAsync()
{
// Prevent a race condition
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (IsRunning)
{
return;
}
while (!await ConnectAsync().ConfigureAwait(false))
{
}
IsRunning = true;
Debug.Assert(_clientWebSocket != null);
_tokenSource = new CancellationTokenSource();
_processingSend = ProcessSendAsync(_clientWebSocket, _tokenSource.Token);
_processingData = ProcessDataAsync(_tokenSource.Token);
_processingReceive = ProcessReceiveAsync(_clientWebSocket);
}
finally
{
_semaphore.Release();
}
}
public async Task StopAsync()
{
if (!IsRunning)
{
return;
}
_logger.LogDebug("Stopping");
try
{
if (_clientWebSocket is { State: not (WebSocketState.Aborted or WebSocketState.Closed or WebSocketState.CloseSent) })
{
await _clientWebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
}
}
catch
{
}
await _processingReceive.ConfigureAwait(false);
_logger.LogDebug("Stopped");
}
private async ValueTask<bool> ConnectAsync()
{
_logger.LogDebug("Connecting");
var ws = new ClientWebSocket();
try
{
await ws.ConnectAsync(new Uri(_url), CancellationToken.None).ConfigureAwait(false);
Connected?.Invoke(this, EventArgs.Empty);
}
catch (Exception) // WebSocketException or TaskCanceledException
{
ws.Dispose();
return false;
}
_clientWebSocket = ws;
_logger.LogDebug("Connected");
return true;
}

The while loop currently blocks the thread
Are you sure? because ClientWebSocket.ConnectAsync explicitly states
This operation will not block. The returned Task object will complete
after the connect request on the ClientWebSocket instance has
completed.
So ConnectAsync should not block, and therefore neither the while loop. But even if it is non-blocking it might still consume significant CPU usage. Or something might throw before you even call ClientWebSocket.ConnectAsync, and since you just eat all the exceptions you will never know.
make it possible for us to call StopAsync during StartAsync's execution and cancel that process of retrying forever
You should be careful when stopping some service in a threaded or async context. Since some other task might find that the needed resource have been disposed.
while (!await ConnectAsync().ConfigureAwait(false) && !_tokenSource.IsCancellationRequested)
The problem with this is that the loop will exit and continue running the method if no connection has been established. Because of this it is recommended to use ThrowIfCancellationRequested, even if it is somewhat painful to use exceptions for flow control.
My recommendations would be to make StartAsync take a cancellationToken that aborts the connection process. This method should return an object representing the connection, i.e. Task<MyConnection>. This connection object should be disposable. Stopping the connection could be done like
// Create connection
var cts = new CancellationTokenSource();
var myStartAsyncConnectionTask = StartAsync(cts.Token);
// Close connection
cts.Cancel();
try{
var myConnection = await myStartAsyncConnectionTask;
myConnection.Dispose();
}
catch(OperationCancelledException)
{
...
}
That should work regardless of what state the connection is in. If a connection has been established the cancellation will do nothing, and the object will be disposed. If it has failed to connect, awaiting the task should throw. Note that StartAsync need to be written so that it cleans up any created resource in case the method throws any exception at any stage.

Related

How to cancel an async connect method after X seconds in C#

I am creating a mqtt client in C# with the libray MQTTNet.
I wan't my client to connect to a broker and stop after 1 second if doesn't succeed.
Here is the function I made below.
private async Task TryConnect(MqttClientOptions options)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
mqttClient!.ConnectAsync(options, tokenSource.Token);
await Task.Delay(1000);
tokenSource.Cancel();
}
The method is working but it gives me a warning when I call the method ConnectAsync because I am not using an await operator before the call. And if I use the await operator the method will continue until it will raise an error.
Is there a way to do this without warnings ? Because even if it is working I have the feeling that this is not the better way to do it and that there is a cleaner way.
Thank you for your help,
Emmanuel
You should probably use CancelAfter:
CancellationTokenSource tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(1));
await mqttClient!.ConnectAsync(options, tokenSource.Token);
An alternative would be to store the task from connectAsync and await if after the cancel call.
Note that in either case you are not guaranteed that the connection will actually cancel, it entirely depends on the ConnectAsync-implementation. In some cases it might be approrpiate to use Task.WhenAny to await either the connection, or a Task.Delay, i.e. you do no longer care about the connection after the timeout. You should probably also catch OperationCancelledException, since that is the standard method to communicate that the operation actually was cancelled.
You can specify a timeout for a cancellation token:
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
If you're cancelling it like this you will need to catch a TaskCanceledException, but you can ignore the cancellation if you want.
private async Task TryConnect(MqttClientOptions options)
{
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
try
{
mqttClient!.ConnectAsync(options, tokenSource.Token);
}
catch (TaskCanceledException)
{
// Do nothing.
}
}
Alternatively you could return a bool to indicate whether the connection was successful:
private async Task<bool> TryConnect(MqttClientOptions options)
{
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
try
{
mqttClient!.ConnectAsync(options, tokenSource.Token);
}
catch (TaskCanceledException)
{
return false;
}
return true;
}

Timeout and stop a Task

I have a console app,
{
StartThread();
//must be true, windows system wants to know it is started
return true;
}
I'm trying to create a safety timeout function for this Task. But the task keeps running...
The method DoSomething calls other async methods and awaits their result
Do anyone have an idea why my task don't stop? Maybe a good code example on what to do
public async void StartThread()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
try
{
var timeout = 1000;
Task task = new Task(() => DoSomething(token), token);
task.Start();
if (await Task.WhenAny(task, Task.Delay(timeout, token)) == task)
{
if (token.IsCancellationRequested)
task.Dispose();
await task;
if (task.IsCompleted)
{
task.Dispose();
tokenSource.Cancel();
tokenSource.Dispose();
}
else
{
log.WriteToFile("Timeout_ ");
}
}
else
tokenSource.Cancel();
}
catch (Exception e)
{
Console.WriteLine("--StartThread ...there is an exception----");
}
finally
{
Thread.Sleep(300000); // 5 minutter
StartThread();
}
}
While not create CancellationTokenSource from given timeout?
var timeout = 1000;
//DONE: don't forget to dispose CancellationTokenSource instance
using (var tokenSource = new CancellationTokenSource(timeout)) {
try {
var token = tokenSource.Token;
//TODO: May be you'll want to add .ConfigureAwait(false);
Task task = Task.Run(() => DoSomething(token), token);
await task;
// Completed
}
catch (TaskCanceledException) {
// Cancelled due to timeout
log.WriteToFile("Timeout_ ");
}
catch (Exception e) {
// Failed to complete due to e exception
Console.WriteLine("--StartThread ...there is an exception----");
//DONE: let's be nice and don't swallow the exception
throw;
}
}
You should hardly ever Dispose a Task, since iot is managed by C# internals and it is taken care of. Also, you Dispose way too eagerly, for example:
if (token.IsCancellationRequested)
task.Dispose();
await task;
I do not think so you want still await task if it cancelled and disposed. I guess it will not work at all.
Also if you use async, do not mix blocking calls such as Thread.Sleep - that can lead to disaster...
After all, you use cancellation token with some delay task to imitate a timeout - that's OK, but why do put unnecessary code, when you have great API at hand. Just make use of special contructor of CancellationTokenSource:
public CancellationTokenSource (int millisecondsDelay);
Here's the docs
After a timeout you are setting the CancellationToken and then immediately sleeping the thread for 5 minutes. Thus DoSomething() never gets a chance to continue running and react to the token being cancelled.

Using Task.Delay within Task.Run

I have a Windows Service that monitors my application by running a couple of tests every second. A bug report has been submitted that said that the service stoppes running after a while, and I'm trying to figure out why.
I suspect that the code below is the culprit, but I have trouble understanding exactly how it works. The ContinueWith statement has recently been commented out, but I dont know if it is needed
private Task CreateTask(Action action)
{
var ct = _cts.Token;
return Task.Run(async () =>
{
ct.ThrowIfCancellationRequested();
var sw = new Stopwatch();
while (true)
{
sw.Restart();
action();
if (ct.IsCancellationRequested)
{
_logger.Debug("Cancellation requested");
break;
}
var wait = _settings.loopStepFrequency - sw.ElapsedMilliseconds;
if (wait <= 0) // No need to delay
continue;
// If ContinueWith is needed wrap this in an ugly try/catch
// handling the exception
await Task.Delay(
(int)(_settings.loopStepFrequency - sw.ElapsedMilliseconds),
ct); //.ContinueWith(tsk => { }, ct);
}
_logger.Debug("Task was cancelled");
}, _cts.Token);
}
Are there any obvious problems with this code?
Are there any obvious problems with this code?
The one that jumps out to me is the calculation for the number of milliseconds to delay. Specifically, there's no floor. If action() takes an unusually long time, then the task could fail in a possibly unexpected way.
There are several ways for the task to complete in either a cancelled or failed state, or it can delay forever:
The task can be cancelled before the delegate begins, due to the cancellation token passed to Task.Run.
The task can be cancelled by the ThrowIfCancellationRequested call.
The task can complete successfully after being cancelled, due to the IsCancellationRequested logic.
The task can be cancelled by the cancellation token passed to Task.Delay.
The task may fail with an ArgumentOutOfRangeException if _settings.loopStepFrequency - sw.ElapsedMilliseconds is less than -1. This is probably a bug.
The task may delay indefinitely (until cancelled) if _settings.loopStepFrequency - sw.ElapsedMilliseconds happens to be exactly -1. This is probably a bug.
To fix this code, I recommend two things:
The code is probably intending to do await Task.Delay((int) wait, ct); instead of await Task.Delay((int)(_settings.loopStepFrequency - sw.ElapsedMilliseconds), ct);. This will remove the last two conditions above.
Choose one method of cancellation. The standard pattern to express cancellation is via OperationCanceledExcpetion; this is the pattern used by ThrowIfCancellationRequested and by Task.Delay. The IsCancellationRequested check is using a different pattern; it will successfully complete the task on cancellation, instead of cancelling it.
There are so many problems with this code, that makes more sense to rewrite it than attempt to fix it. Here is a possible way to rewrite this method, with some (possibly superfluous) argument validation added:
private Task CreateTask(Action action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
var ct = _cts.Token;
var delayMsec = _settings.loopStepFrequency;
if (delayMsec <= 0) throw new ArgumentOutOfRangeException("loopStepFrequency");
return Task.Run(async () =>
{
while (true)
{
var delayTask = Task.Delay(delayMsec, ct);
action();
await delayTask;
}
}, ct);
}
The responsibility for logging a possible exception/cancellation belongs now to the caller of the method, that (hopefully) awaits the created task.
var task = CreateTask(TheAction);
try
{
await task; // If the caller is async
//task.GetAwaiter().GetResult(); // If the caller is sync
_logger.Info("The task completed successfully");
}
catch (OperationCanceledException)
{
_logger.Info("The task was canceled");
}
catch (Exception ex)
{
_logger.Error("The task failed", ex);
}

Is the correct way to cancel a cancellation token used in a task?

I have code that creates a cancellation token
public partial class CardsTabViewModel : BaseViewModel
{
public CancellationTokenSource cts;
public async Task OnAppearing()
{
cts = new CancellationTokenSource(); // << runs as part of OnAppearing()
Code that uses it:
await GetCards(cts.Token);
public async Task GetCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts);
await CheckAvailability();
}
}
and code that later cancels this Cancellation Token if the user moves away from the screen where the code above is running:
public void OnDisappearing()
{
cts.Cancel();
Regarding cancellation, is this the correct way to cancel the token when it's being used in a Task?
In particular I checked this question:
Use of IsCancellationRequested property?
and it's making me think that I am not doing the cancel the correct way or perhaps in a way that can cause an exception.
Also, in this case after I have cancelled then should I be doing a cts.Dispose()?
In general I see a fair use of Cancel Token in your code, but according the Task Async Pattern your code could be not cancelling immediately.
while (!ct.IsCancellationRequested)
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts);
await CheckAvailability(); //Your Code could be blocked here, unable to cancel
}
For responding right away, the blocking code also should be cancelled
await CheckAvailability(ct); //Your blocking code in the loop also should be stoped
It is up to you if you must Dispose, if there are many memory resources been reserved in the interrupted code, you should do it.
CancellationTokenSource.Cancel() is a valid way to start cancellation.
Polling ct.IsCancellationRequested avoids throwing OperationCanceledException.
Because its polling, it requires an iteration of the loop to complete before it will respond to the cancellation request.
If GetViewablePhrases() and CheckAvailability() can be modified to accept a CancellationToken, this may make cancellation faster to respond, at the cost of having OperationCanceledException thrown.
"should I be doing a cts.Dispose()?" is not that straightforward...
"Always dispose IDisposables ASAP"
Is more of a guideline than a rule.
Task itself is disposable, yet hardly ever directly disposed in code.
There are cases (when WaitHandle or cancellation callback handlers are used) where disposing cts would free a resource / remove a GC root which otherwise would only be freed by a Finalizer.
These don't apply to your code as it stands but may in future.
Adding a call to Dispose after cancelling would guarantee that these resources are freed promptly in future versions of the code.
However, you'd have to either wait for the code that uses cts to finish before calling dispose, or modify the code to deal with ObjectDisposedException from use of cts (or its token) after disposal.
I would recommend you to take a look on one of the .net classes to fully understand how to handle wait methods with CanncelationToken, I picked up SeamaphoreSlim.cs
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
{
CheckDispose();
// Validate input
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException(
"totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong"));
}
cancellationToken.ThrowIfCancellationRequested();
uint startTime = 0;
if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0)
{
startTime = TimeoutHelper.GetTime();
}
bool waitSuccessful = false;
Task<bool> asyncWaitTask = null;
bool lockTaken = false;
//Register for cancellation outside of the main lock.
//NOTE: Register/deregister inside the lock can deadlock as different lock acquisition orders could
// occur for (1)this.m_lockObj and (2)cts.internalLock
CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this);
try
{
// Perf: first spin wait for the count to be positive, but only up to the first planned yield.
// This additional amount of spinwaiting in addition
// to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios.
//
SpinWait spin = new SpinWait();
while (m_currentCount == 0 && !spin.NextSpinWillYield)
{
spin.SpinOnce();
}
// entering the lock and incrementing waiters must not suffer a thread-abort, else we cannot
// clean up m_waitCount correctly, which may lead to deadlock due to non-woken waiters.
try { }
finally
{
Monitor.Enter(m_lockObj, ref lockTaken);
if (lockTaken)
{
m_waitCount++;
}
}
// If there are any async waiters, for fairness we'll get in line behind
// then by translating our synchronous wait into an asynchronous one that we
// then block on (once we've released the lock).
if (m_asyncHead != null)
{
Contract.Assert(m_asyncTail != null, "tail should not be null if head isn't");
asyncWaitTask = WaitAsync(millisecondsTimeout, cancellationToken);
}
// There are no async waiters, so we can proceed with normal synchronous waiting.
else
{
// If the count > 0 we are good to move on.
// If not, then wait if we were given allowed some wait duration
OperationCanceledException oce = null;
if (m_currentCount == 0)
{
if (millisecondsTimeout == 0)
{
return false;
}
// Prepare for the main wait...
// wait until the count become greater than zero or the timeout is expired
try
{
waitSuccessful = WaitUntilCountOrTimeout(millisecondsTimeout, startTime, cancellationToken);
}
catch (OperationCanceledException e) { oce = e; }
}
// Now try to acquire. We prioritize acquisition over cancellation/timeout so that we don't
// lose any counts when there are asynchronous waiters in the mix. Asynchronous waiters
// defer to synchronous waiters in priority, which means that if it's possible an asynchronous
// waiter didn't get released because a synchronous waiter was present, we need to ensure
// that synchronous waiter succeeds so that they have a chance to release.
Contract.Assert(!waitSuccessful || m_currentCount > 0,
"If the wait was successful, there should be count available.");
if (m_currentCount > 0)
{
waitSuccessful = true;
m_currentCount--;
}
else if (oce != null)
{
throw oce;
}
// Exposing wait handle which is lazily initialized if needed
if (m_waitHandle != null && m_currentCount == 0)
{
m_waitHandle.Reset();
}
}
}
finally
{
// Release the lock
if (lockTaken)
{
m_waitCount--;
Monitor.Exit(m_lockObj);
}
// Unregister the cancellation callback.
cancellationTokenRegistration.Dispose();
}
// If we had to fall back to asynchronous waiting, block on it
// here now that we've released the lock, and return its
// result when available. Otherwise, this was a synchronous
// wait, and whether we successfully acquired the semaphore is
// stored in waitSuccessful.
return (asyncWaitTask != null) ? asyncWaitTask.GetAwaiter().GetResult() : waitSuccessful;
}
You can also view the whole class here, https://referencesource.microsoft.com/#mscorlib/system/threading/SemaphoreSlim.cs,6095d9030263f169

Task await fails

I am starting a HubConnection. First I get a new Instance of HubConnection,
afterwards I create a new IHubProxy with Name = "fileHub" so far so good.
My problem is located at (or in) the awaitable Function ContinueWith, I try to start the Connection, and write to the console wheater the start was successful or not.
The connection.Start() is successful and "Connected" is written to the console.
Code added after the Console.WriteLine("Connected") is executed without problems, too.
But the Task never finishes, so the Client Class calling the HandleSignalRAsync() Method waits for the completion unsuccessfully.
Adding a return; or task.dispose(); does not solve my issue.
public static async Task HandleSignalRAsync()
{
connection = new HubConnection("http://localhost:12754");
myHub = connection.CreateHubProxy("fileHub");
await connection.Start().ContinueWith(
task =>
{
if (task.IsFaulted)
{
var ex = task.Exception.GetBaseException();
Console.WriteLine("There was an error opening the connection: {0}", ex);
}
else
{
Console.WriteLine("Connected");
}
});
}
I call the Method with the TaskAwaiter in another Class of my Solution:
Functions.HandleSignalRAsync().GetAwaiter().GetResult();
calling it with:
Functions.HandleSignalRAsync().Wait();
does not work, too.
But the Task never finishes
Because both examples synchronously block, causing your code to deadlock. You shouldn't be blocking on async code.
You'll need to properly asynchronously wait on HandleSignalRAsync:
await Functions.HandleSignalRAsync().ConfigureAwait(false);
If you're already using async-await, there's no advantage to use a continuation style with ContinueWith, you can simply wrap the execution in a try-catch statement and await inside:
try
{
await connection.Start().ConfigureAwait(false);
Console.WriteLine("Connected");
}
catch (Exception e)
{
Console.WriteLine("There was an error opening the connection: {0}", e);
}

Categories

Resources