C# How is the CancellationToken used with SqlConnection.OpenAsync(token)? - c#

I'm attempting to use the CancellationToken with SqlConnection.OpenAsync() to limit the amount of time that the OpenAsync function takes.
I create a new CancellationToken and set it to cancel after say 200 milliseconds. I then pass it to OpenAsync(token). However this function still can take a few seconds to run.
Looking at the documentation I cant really see what I'm doing wrong.
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.openasync?view=netframework-4.7.2
Here is the code I'm using:
private async void btnTestConnection_Click(object sender, EventArgs e)
{
SqlConnection connection = new SqlConnection(SQLConnectionString);
Task.Run(() => QuickConnectionTest(connection)).Wait();
}
public async Task QuickConnectionTest(SqlConnection connection)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
source.CancelAfter(200);
ConnectionOK = false;
try
{
using (connection)
{
await connection.OpenAsync(token);
if (connection.State == System.Data.ConnectionState.Open)
{
ConnectionOK = true;
}
}
}
catch (Exception ex)
{
ErrorMessage = ex.ToString();
}
}
I was expecting OpenAsync() to end early when the CancellationToken to throw a OperationCanceledException when 200ms had passed but it just waits.
To replicate this I do the following:
Run the code: Result = Connection OK
Stop the SQL Service
Run the code: hangs for the length of connection.Timeout

Your code seems correct to me. If this does not perform cancellation as expected then SqlConnection.OpenAsync does not support reliable cancellation. Cancellation is cooperative. If it's not properly supported, or if there is a bug, then you have no guarantees.
Try upgrading to the latest .NET Framework version. You can also try .NET Core which seems to lead the .NET Framework. I follow the GitHub repositories and have seen multiple ADO.NET bugs related to async.
You can try calling SqlConnection.Dispose() after the timeout passes. Maybe that works. You also can try using the synchronous API (Open). Maybe that uses a different code path. I believe it does not, but it is worth a try.
In case that does not work either you need to write your code so that your logic proceeds even if the connection task has not completed. Pretend that it has completed and let it linger in the background until it completes by itself. This can cause increased resource usage but it might be fine.
In any case consider opening an issue in the GitHub corefx repository with a minimal repro (like 5 lines connecting to example.com). This should just work.

The CancelationToken works the same for everyone else that it does for you in your own code.
Example:
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("main");
var cts = new CancellationTokenSource();
var task = SomethingAsync(cts.Token);
cts.Cancel();
await task;
Console.WriteLine("Complete");
Console.ReadKey();
}
static async Task SomethingAsync(CancellationToken token)
{
Console.WriteLine("Started");
while (!token.IsCancellationRequested)
{
await Task.Delay(2000); //didn't pass token here because we want to simulate some work.
}
Console.WriteLine("Canceled");
}
}
//**Outputs:**
//main
//Started
//… then ~2 seconds later <- this isn't output
//Canceled
//Complete
The OpenAsync method may require some optimization but don't expect a call to Cancel() to immediately cancel any Task. It's just a marshalled flag to let the unit of work within the Task know that the caller wants to cancel it. The work inside the Task gets to choose how and when to cancel. If it's busy when you set that flag then you just have to wait and trust the Task is doing what it needs to do to wrap things up.

Related

Concise way to await a canceled Task?

I find myself writing code like this a lot:
try
{
cancellationTokenSource.Cancel();
await task.ConfigureAwait(false); // this is the task that was cancelled
}
catch(OperationCanceledException)
{
// Cancellation expected and requested
}
Given that I requested the cancellation, it is expected and I'd really like the exception to be ignored. This seems like a common case.
Is there a more concise way to do this? Have I missed something about cancellation? It seems like there should be a task.CancellationExpected() method or something.
There is a built-in mechanism, the Task.WhenAny method used with a single argument, but it's not very intuitive.
Creates a task that will complete when any of the supplied tasks have completed.
await Task.WhenAny(task); // await the task ignoring exceptions
if (task.IsCanceled) return; // the task is completed at this point
var result = await task; // can throw if the task IsFaulted
It is not intuitive because the Task.WhenAny is normally used with at least two arguments. Also it is slightly inefficient because the method accepts a params Task<TResult>[] tasks argument, so on every invocation an array is allocated in the heap.
I don't think there is anything built-in, but you could capture your logic in extension methods (one for Task, one for Task<T>):
public static async Task IgnoreWhenCancelled(this Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
}
public static async Task<T> IgnoreWhenCancelled<T>(this Task<T> task)
{
try
{
return await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return default;
}
}
Then you can write your code simpler:
await task.IgnoreWhenCancelled();
or
var result = await task.IgnoreWhenCancelled();
(You might still want to add .ConfigureAwait(false) depending on your synchronization needs.)
I assume whatever task is doing uses CancellationToken.ThrowIfCancellationRequested() to check for cancellation. That throws an exception by design.
So your options are limited. If task is an operation you wrote, you could make it not use ThrowIfCancellationRequested() and instead check IsCancellationRequested and end gracefully when needed. But as you know, the task's status won't be Canceled if you do that.
If it uses code you didn't write, then you don't have a choice. You'll have to catch the exception. You can use extension methods to avoid repeating code (Matt's answer), if you want. But you'll have to catch it somewhere.
The cancellation pattern available in C# in called cooperative cancellation.
This basically means that, in order to cancel any operation, there should be two actors which need to collaborate. One of them is the actor requesting the cancellation and the other is the actor listening to cancellation requests.
In order to implement this pattern you need an instance of CancellationTokenSource, which is an object that you can use in order to get an instance of CancellationToken. The cancellation is requested on the CancellationTokenSource instance and is propagated to the CancellationToken.
The following piece of code shows you this pattern in action and hopefully clarifies your doubt about cancellation:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp2
{
public static class Program
{
public static async Task Main(string[] args)
{
using (var cts = new CancellationTokenSource())
{
CancellationToken token = cts.Token;
// start the asyncronous operation
Task<string> getMessageTask = GetSecretMessage(token);
// request the cancellation of the operation
cts.Cancel();
try
{
string message = await getMessageTask.ConfigureAwait(false);
Console.WriteLine($"Operation completed successfully before cancellation took effect. The message is: {message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("The operation has been canceled");
}
catch (Exception)
{
Console.WriteLine("The operation completed with an error before cancellation took effect");
throw;
}
}
}
static async Task<string> GetSecretMessage(CancellationToken cancellationToken)
{
// simulates asyncronous work. notice that this code is listening for cancellation
// requests
await Task.Delay(500, cancellationToken).ConfigureAwait(false);
return "I'm lost in the universe";
}
}
}
Pay attention to the comment and notice that all the 3 outputs for the program are possible.
There is no way to predict which of them will be the actual program result.
The point is that when you await for the task completion you don't know what actually is going to happen. The operation may succeeds or fails before the cancellation took effect, or maybe the cancellation request can be observed by the operation before it runs to completion or fails for an error. From the calling code point of view, all these outcomes are possible and you have no way to make a guess. You need to handle all cases.
So, basically, your code is correct and you are handling the cancellation the way you should.
This book is an excellent reference to learn these things.
My final solution was to create an extension method as suggested by Matt Johnson-Pint. However, I return a boolean indicating whether the task was canceled as shown in Vasil Oreshenski's answer.
public static async Task<bool> CompletionIsCanceledAsync(this Task task)
{
if (task.IsCanceled) return true;
try
{
await task.ConfigureAwait(false);
return false;
}
catch (OperationCanceledException)
{
return true;
}
}
This method has been fully unit tested. I picked the name to be similar to the WaitForCompletionStatus() method in the ParallelExtensionsExtras sample code and the IsCanceled property.
If you are expecting the task to be cancelled BEFORE the await you should check the state of the cancellation token source.
if (cancellationTokenSource.IsCancellationRequested == false)
{
await task;
}
EDIT: As mentioned in the comments this won't do any good if the task is cancelled while awaited.
EDIT 2: This approach is overkill because it acquires additional resource - in hot path this may have performance hit. (i am using SemaphoreSlim but you can use another sync. primitive with the same success)
This is an extension method over existing task. The extension method will return new task which holds information if the original task was cancelled.
public static async Task<bool> CancellationExpectedAsync(this Task task)
{
using (var ss = new SemaphoreSlim(0, 1))
{
var syncTask = ss.WaitAsync();
task.ContinueWith(_ => ss.Release());
await syncTask;
return task.IsCanceled;
}
}
Here is a simple usage:
var cancelled = await originalTask.CancellationExpectedAsync();
if (cancelled) {
// do something when cancelled
}
else {
// do something with the original task if need
// you can acccess originalTask.Result if you need
}
How it works:
Overall it waits for the original task to complete and returns information if was cancelled. The SemaphoraSlim is usually used to limit the access to some resource(expensive) but in this case i am using it to await until the original task has finished.
Notes:
It does not returns the original task. So if you need something that has been returned from it you should inspect the original task.

C# UWP cancelling a task + signaling child

I realize this is not the first thread on cancelling tasks, but I didn't find anything suitable so far which fits my needs.
Here's the case:
I've got a modular system in which modules are initialized by a controller class, dynamically based on configuration.
Each module implements a Connect() method. The method itself is black box, but for background info: some modules attempt to create Bluetooth connections.
If connecting takes too long, I want to cancel the attempt and retry it in 5 minutes.
At first I put the retry logic in the module implementation. That works fine, but becomes repetitive over modules and doesn't really belong to the core responsibility of the module. So I then considered putting the logic in the controller, but I'm struggling to find a nice implementation.
The issue here is that I not only want to cancel the task, I want to do it in a nice way. If there's objects to close / dispose, the module should be able to do so. Also, the module should be able to set its status to "Disconnected" instead of "Connecting" (and I rather have the module do that than have a public method so externally change the status). This was all easy when the Connect and timeout logic is inside the module, but becomes more challenging introducing an outside timeout.
Here's the flow I foresee when a module does not respond in time:
Controller creates a new module instance
Controller calls Connect() in order to connect the module
Controller also initializes a timer to monitor the module finishes connecting in 30 seconds
Timer goes off, module hasn't finished it's work yet
Connect() method should be notified of Cancellation, preferably by throwing an exception inside of the Connect method which can be caught and handled.
Exception handler can then clean-up things neatly and the method returns.
The above flow doesn't exist as far as I've found. Cancellation tokens work differently and require me to have events or polling in place. That's nice, but then I'm back to including this logic into my modules so why not just do the entire retry thing in there to begin with. So I wonder whether there is any sweet pattern out there that allows to me to this.
I'm building, by the way, for Windows 10 UWP. I haven't included any code on purpose because what I have at the moment is crap anyway.
Don't get what is the problem with CancellationToken your async operation supports cancelation or doesn't there is no way to stop code that is executing unless you kill the thread or process.
Here is a sample of timeout.
public static async Task Run()
{
var module = new Module();
//No timeout
await module.Connect(1, CancelAfter(2000));
try
{
// Timeout
await module.Connect(5, CancelAfter(1000));
}
catch (Exception)
{
module.Dispose();
}
}
public static CancellationToken CancelAfter(int millisecondsDelay)
{
var token = new CancellationTokenSource();
token.CancelAfter(millisecondsDelay);
return token.Token;
}
public class Module : IDisposable
{
public async Task Connect(int count, CancellationToken cancel)
{
for (int i = 0; i < count; i++)
{
//This is just to simulte some work Task.Delay can be canceled as well with Task.Delay(500,cancel)
await Task.Delay(500);
cancel.ThrowIfCancellationRequested();
}
}
public void Dispose()
{
}
}
You can time out any task with
public static class TaskTimeout
{
public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
if (task != await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
{ throw new TimeoutException(); }
}
public static async Task<T> TimeoutAfter<T>(this Task<T> task, int millisecondsTimeout)
{
if (task != await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
{ throw new TimeoutException(); }
else { return task.Result; }
}
}
But this won't cancel you connection only if Connect is cancelable it can be canceled. Most Async calls in .net have CancellationToken implemented so if your connection has it use that.

TcpClient.ConnectAsync or Socket.BeginConnect with non-blocking timeout setting

All solutions I found so far are based on WaitOne:
How to configure socket connect timeout
or spawning a worker thread
For me, blocking the thread with WaitOne defeats the purpose of async methods. Spawning another thread is not much better (as async model strives to use as less threads as possible).
Is there any other solution which would still let me abort the connection attempt without blocking the current thread or spawning another one?
I'm developing a library used by external code which has no knowledge of what's going on inside my library (the code just calls my ConnectAsync method and I do the rest including TcpClient.ConnectAsync and so on). The external code can be anything (web app, desktop app, Windows service, whatever). Ideally, the solution should not require the external code to do anything to abort the operation beyond setting .Timeout property of my class. However, if it's the only the way to avoid blocks or worker threads when implementing custom connection timeout, I'd be grateful to see which options I have (in terms of async/await model).
TcpClient.SendAsync doesn't receive a CancellationToken so it can't really be canceled (How do I cancel non-cancelable async operations?). You can use the WithTimeout extensions method:
public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously);
return Task.WhenAny(task, timeoutTask).Unwrap();
}
This doesn't cancel the original operation though, only allows your code to behave as if it did. The abandoned operation will linger forever unless handled explicitly.
To actually cancel the underlying operation you should make sure to call Dispose on the TcpClient (preferably via a using scope). That would make the abandoned task throw an ObjectDisposedException (or others) so be aware of that.
You can take a look at my answer here about using a TimeoutScope:
try
{
var client = new TcpClient();
using (client.CreateTimeoutScope(TimeSpan.FromSeconds(2)))
{
var result = await client.ConnectAsync();
// Handle result
}
}
catch (ObjectDisposedException)
{
return null;
}
If you create a second task for the timeout (Task.Delay does nicely), then you can use Task.WhenAny to complete as soon as either your task, or the timeout completes.
var timeout = Task.Delay(whatever);
var mightTimeout = Task.WhenAny(new {} { timeout, originalTask });
// let mightTimeout complete by whatever method (eg. async)
if (mightTimeout == timeout) {
// Timeout!!
// Put abort code in here.
}
I found a solution using
Await Task.WhenAny
Task.WhenAny will finish whenever any of the task included finishes first.
Put it inside an async function
Here's an example that works for me:
Public Async Function TCPConnectionAsync(HostIpAddress, Port) As Task(Of String)
Dim client As New TcpClient
Await Task.WhenAny(client.ConnectAsync(HostIpAddress, Porta), Task.Delay(3000))
'this above will not block because function is async,
'whenever the connection is successful or Task.Delay expires the task will end
' proceeding next, where you can check the connection
If client.Connected = False Then 'this will be evaluated after 3000ms
client.Close()
return "not connected"
else
'do here whatever you need with the client connection
client.Close()
return "all done"
End If
End Sub
For those who want to use asynchronous solution with async/await with timeout support:
public static async Task<bool> PingHostAndPort(string host, int port, int timeout)
{
using (var tcpClient = new TcpClient())
{
Task connectTask = tcpClient.ConnectAsync(host, port);
Task timeoutTask = Task.Delay(timeout);
// Double await is required to catch the exception.
Task completedTask = await Task.WhenAny(connectTask, timeoutTask);
try
{
await completedTask;
}
catch (Exception)
{
return false;
}
if (timeoutTask.IsCompleted)
{
return false;
}
return connectTask.Status == TaskStatus.RanToCompletion && tcpClient.Connected;
};
}
Note that you can use this logic (utilizing the timeoutTask) from any operations/methods that miss a timeout parameter.

getAsync("Text") vs getAsync("text", cancellationToken)

I am using the oneDrive API aka Live SDK. using c# and XAML you can issue http GET request using the above mentioned method like so:
case A:
public async void methodA()
{
try
{
var meResult = await connectClient.GetAsync("me");
}
catch(Exception ex)
{
//exception handling
}
doSomething();
}
Case B:
public async void methodB()
{
try
{
var meResult = await connectClient.GetAsync("me", token);
}
catch(Exception ex)
{
//exception handling
}
doSomething();
}
where token is cancellationToken; that will wait for specific time and then cancel the request.
if there is no internet connection:
In case A the methodA() will hang on the getAsync and never progress so the doSomething() method is never called.
In case B the methodB() will handle the exception when the cancellationToken cancel the call then progress to the doSomething() method.
Now my concern and my question is:
I am afraid that keeping the await call hanging there will mean locking one thread and will affect the performance especially if the user clicked the button many times to call the methodA().
Is my concern justified?
is there a way for the .NET runtime to know that this awaited operation is timed out?? and eventually cancel it?
If you want to pass in a CancellationToken that times out after a certain amount of time, there is a constructor for CancellationTokenSource that does just that:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var meResult = await connectClient.GetAsync("me", cts.Token);
By the way, even if you don't do this, a .NET thread won't be locked while you wait for GetAsync to finish - that's one of the direct benefits of using await instead of .Result.
Related article by Stephen Toub: Coalescing CancellationTokens from Timeouts

Running several infinite loops with async/await

I am developing android messanger app based on xamarin and .net 5 async/awaits.
In my app i have producer/consumer pattern for processing messages which is made on infinite loops.
for example ReadTcpClientAsync producer:
async Task ReadTcpClientAsync(CancellationToken cancellationToken)
{
cde.Signal();
while (!cancellationToken.IsCancellationRequested)
{
byte[] buffer = await atc.ReadAsync(cancellationToken);
// queue message...
}
}
or SendStatementsAsync consumer which deque messages and awaits WriteAsync
private async Task SendStatementsAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var nextItem = await _outputStatements.Take();
cancellationToken.ThrowIfCancellationRequested();
// misc ...
await atc.WriteAsync(call.Serialize());
}
}
and some consumers just await on Take calls
var update = await _inputUpdateStatements.Take();
this construction works pretty well on tests, but there is one method where i think i made a huge mistake.
this method intent to run entire client backend, starting 3 pro/con while (true) loops simultaneously.
here it is:
public async Task RunAsync()
{
_isRunning = true;
_progress.ProgressChanged += progress_ProgressChanged;
await InitMTProto(_scheme).ConfigureAwait(false); // init smth...
// various init stuf...
await atc.ConnectAsync().ConfigureAwait(false); // open connection async
// IS IT WRONG?
try
{
await Task.WhenAny(SendStatementsAsync(_cts.Token),
ReadTcpClientAsync(_cts.Token),
ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);
}
catch (OperationCanceledException oce)
{
}
catch (Exception ex)
{
}
}
Forget about android for now, think any UI (WinForm, WPF, etc) OnCreate method in UI context to call RunAsync
protected async override void OnCreate(Bundle bundle)
{
// start RA
await client.RunAsync()
// never gets here - BAD, but nonblock UI thread - good
Debug.WriteLine("nevar");
}
so, as you can see there is a problem. I can't do anything after RunAsync await call because it will never returns from Task.WhenAny(...). And i need perform status check there, but i need this pro/cons methods started, because my check wait on ManualResetEvent for it:
if (!cde.Wait(15000))
{
throw new TimeoutException("Init too long");
}
Also, my check is async too, and it works like a charm :)
public async Task<TLCombinatorInstance> PerformRpcCall(string combinatorName, params object[] pars)
{
// wait for init on cde ...
// prepare call ...
// Produce
ProduceOutput(call);
// wait for answer
return await _inputRpcAnswersStatements.Take();
}
I think i should use another approach for starting this infinite loops, but i already have async Task methods all the way - so i really have no idea what to do.
Any help please?
Ok, after a lot of reading (nothing found) and #svick's advice i decided to call this methods without "await" as separate Task.Run's.
Aso i decided to run it in ThreadPool.
My final code is:
try
{
/*await Task.WhenAny(SendStatementsAsync(_cts.Token),
ReadTcpClientAsync(_cts.Token),
ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);*/
Task.Run(() => SendStatementsAsync(_cts.Token)).ConfigureAwait(false);
Task.Run(() => ReadTcpClientAsync(_cts.Token)).ConfigureAwait(false);
Task.Run(() => ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);
Trace.WriteLineIf(clientSwitch.TraceInfo, "Worker threads started", "[Client.RunAsync]");
}
Everything works fine as expected..
i'm not sure what problems it will cause in exception handling, as i know they will be lost
Of course such calls produce warning
Because this call is not awaited, execution of the current method
continues before the call is completed. Consider applying the 'await'
operator to the result of the call.
which can be easily suppressed this way
// just save task into variable
var send = Task.Run(() => SendStatementsAsync(_cts.Token)).ConfigureAwait(false);
Also, if anyone know better solution i will be grateful to hear it.

Categories