I'm doing BLE implementation in Xamarin forms. I want to stop the BLE tasks when the device get disconnected. I used cancellation token for stopping the task and also gave a call to a void method which has return as you can see in the following code:
private async Task ConnectForDevice(ScanData scanData, CancellationTokenSource token)
{
try
{
await _adapter.ConnectToDeviceAsync(_device);
}
catch (DeviceConnectionException ex)
{
await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
disconnected();
token.Cancel();
}
}
public async void disconnected()
{
await Application.Current.MainPage.Navigation.PopAsync();
return;
}
The token.Cancel() should stop the task but it doesn't stop the execution. Instead it goes and executes other async tasks. Is there any way to stop these async task easily? Any suggestion?
You are using the cancellation token wrong and your code does not make a lot of sense.
First you should have a CancellationToken and not a CancellationTokenSource as a parameter in ConnectForDevice if this is the taks you want to cancel. You create a CancellationTokenSource in the class calling ConnectForDevices passen the cancellationsource's token to it, being able to cancel the task from the caller by calling cancellationTaskSource.Cancel().
I don't know the API/Library you are using, but I suppose there is an overload for * _adapter.ConnectToDeviceAsync getting a CancellationToken. There you pass you cancellationToken, which when cancelled will cancel the execution.
This will raise an exception of type TaskCancelledException (if implemented in so in _adapter.ConnectToDevice). I say if implemented, since calling cancellationTokenSource.Cancel() will just mark the token for cancellation, setting IsCancellationRequested=true but somewhere (normally inside the method to be cancelled) token. ThrowIfCancellationRequested() needs to be called or manually checking the state of IsCancellationRequested and throwing or cancelling or whatever, in order to stop the execution.
Also I suppose the ConnectToDeviceAsync method will only connect to the device and return or throw an exception afterwards. You will get no signaling of a disconnection this way. I also suppose the DeviceConnectionException will only raise if an exception when connecting to the device happen, but this does not mean a disconnection, because there was no connection.
Normally there is an event with informs you about this, but can be there is not, so the only way would be polling the connection or sth.
So your code becomes more something like this:
private async Task ConnectForDevice(ScanData scanData, CancellationToken token)
{
try
{
await _adapter.ConnectToDeviceAsync(_device, token);
}
catch (DeviceConnectionException ex)
{
await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
}
catch (TaskCanceledException tce)
{
Console.WriteLine("Scan was cancelled");
}
finally
{
await Disconnect();
}
}
public Task Disconnect() //NEVER USE ASYNC VOID, ONLY IN EVENT HANDLERS
{
return Application.Current.MainPage.Navigation.PopAsync();
}
Update
You are understanding Plugin.BLE wrong.
Here a basic example (not use in production, is for demo)
void Main()
{
//Create your token source
var tcs = new CancellationTokenSource();
//Since we dont await execution goes on to demo cancellation
ScanAndConnectToKnownDevice(/*{yourDeviceId}*/,tcs.Token);
Thread.Sleep(2000)
tcs.Cancel(); //We cancel the process after 2 seconds.
}
async Task ScanAndConnectToKnownDevice(Guid deviceId, CancellationToken cancellationToken)
{
IDevice device;
//CrossBluetoothLE.Current.Adapter has a few events to handle connection changes
//We wire some of them here
//There is an event for when a device disconnects, which
//will be raised when ANY device disconnects
CrossBluetoothLE.Current.Adapter.DeviceDisconnected += OnDeviceDisconnected;
//There is an event for when a device is discovered in scan
CrossBluetoothLE.Current.Adapter.DeviceDiscovered += OnDeviceDiscovered;
//Start scanning process
try
{
await CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync(tcs.Token);
}
catch (TaskCanceledException tce)
{
await UserDialogs.Instance.AlertAsync("Scanning was cancelled");
}
catch (Exception ex)
{
await UserDialogs.Instance.AlertAsync("Error scanning for devices");
}
if (device is null)
{
await UserDialogs.Instance.AlertAsync("Device not found!");
}
else
{
try
{
//This will ONLY CONNECT to the device and then RETURN after connection
//The token you are passing here will cancel THE CONNECTION PROCESS,
//that does not mean device was disconnected, but that there were problems
//CONNECTING to the device
await BluetoothService.ConnectForDevice(device, tcs.Token);
}
catch (DeviceConnectionException ex)
{
await UserDialogs.Instance.AlertAsync("Error connecting to the device!");
}
catch (TaskCanceledException tce)
{
await UserDialogs.Instance.AlertAsync("Connection process was cancelled!");
}
}
}
private async void OnDeviceDisconnected(object sender, DeviceEventArgs e) //Is OK to use async void here
{
if (a.Device.ID == deviceId)
await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
}
private void OnDeviceDiscovered(object sender, DeviceEventArgs e) //Is OK to use async void here
{
if (a.Device.ID == deviceId)
_device = a.Device;
}
Related
I'm trying to use the new .NET 6 Task.WaitAsync(CancellationToken) API.
What I'd like to accomplish is to cancel the waiting for a task, while still being capable of trying to cancel the task itself (it calls an async library and I cannot be sure it will observe the cancellationToken I passed in, or at least not in a timely manner) and avoid to swallow any possible exception it could throw.
So, for example, let's say I want to call an async method:
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
//do something before the call
await Library.DoSomethingAsync(cancellationToken); // Let's pretend
// this is a call to a libary that accepts a cancellationToken,
// but I cannot be 100% sure it will be observed
}
from a button click event handler:
private async void button1_Click(object sender, EventArgs e)
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var tsk = DoSomethingAsync(cts.Token);
try
{
await tsk.WaitAsync(cts.Token);
}
catch (Exception ex) when (ex is OperationCanceledException)
{
tsk.Forget();
}
}
Now I'm sure the await will last 5 seconds max, but when the OperationCanceledException is caught the task could still be running and I don't want to swallow any of the exceptions that it could throw.
So what can I do now if I don't want to await it?
I thought using a FireAndForget extension method like this inside the catch block:
public static async void Forget(this Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception)
{
throw;
}
}
Is this an acceptable pattern, or should I just trust the library and hope it will sooner or later be canceled anyway?
And what if it will never do so, will the Forget method await forever?
You could combine the WaitAsync and Forget functionality in a single extension method like the one below:
public async static Task WaitAsyncAndThenOnErrorCrash(this Task task,
CancellationToken cancellationToken)
{
Task waitTask = task.WaitAsync(cancellationToken);
try { await waitTask; }
catch when (waitTask.IsCanceled) { OnErrorCrash(task); throw; }
static async void OnErrorCrash(Task task)
{
try { await task.ConfigureAwait(false); }
catch when (task.IsCanceled) { } // Ignore overdue cancellation
}
}
Usage example:
private async void button1_Click(object sender, EventArgs e)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
await DoSomethingAsync(cts.Token).WaitAsyncAndThenOnErrorCrash(cts.Token);
}
catch (OperationCanceledException) { } // Ignore
}
In case the DoSomethingAsync completes with error either before or after the cancellation, the application will crash with a popup saying "Unhandled exception has occurred in your application". The user will have the option to continue running the app, by clicking the "Continue" button:
I have a task that is doing an infinite loop.
I have a CancellationToken that I pass both to the Task.Run call as to the actual ExecutePoll function.
I wait for a few seconds and Cancel the token.
There is one Continuation that should run when the task is cancelled.
Turns out this continuation only runs if I explicitly call cancellationToken.ThrowIfCancellationRequested();. If I only break out of the loop the task is always in the status: RanToCompletion
Can anyone share some light as to what I am getting wrong here?
CODE:
using System.Threading;
using System.Threading.Tasks;
namespace TaskCancellationTest
{
class Program
{
private static CancellationTokenSource _pollProcessTokenSource;
static async Task Main(string[] args)
{
InitPollingProcess();
await Task.Delay(5000);
Console.WriteLine("Cancelling loop");
_pollProcessTokenSource.Cancel();
Console.WriteLine("Loop cancelled");
Console.ReadLine();
}
private static void InitPollingProcess()
{
try
{
_pollProcessTokenSource = new CancellationTokenSource();
Task pollForListenerConfigs = Task.Run(async () =>
{
await ExecutePoll(_pollProcessTokenSource.Token);
},
_pollProcessTokenSource.Token);
pollForListenerConfigs.ContinueWith(_ =>
{
Console.WriteLine("Poll process stopped!");
}, TaskContinuationOptions.OnlyOnCanceled);
pollForListenerConfigs.ContinueWith(t =>
{
Console.WriteLine($"Task status: {t.Status}");
});
}
catch (Exception ex)
{
Console.WriteLine($"Poll process failed with Exception:\n {ex.Message}");
}
}
private static async Task ExecutePoll(CancellationToken cancellationToken)
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Exit from poll loop!");
//cancellationToken.ThrowIfCancellationRequested(); // UNCOMMENT TO MAKE CONTINUATION RUN
break;
}
Console.WriteLine("Looping...");
await Task.Delay(1000);
}
}
}
}
There is nothing wrong, it is well explained here:
You can terminate the operation by using one of these options:
By simply returning from the delegate. In many scenarios this is sufficient; however, a task instance that is canceled in this way
transitions to the TaskStatus.RanToCompletion state, not to the
TaskStatus.Canceled state.
By throwing a OperationCanceledException and passing it the token on which cancellation was requested. The preferred way to do this is
to use the ThrowIfCancellationRequested method. A task that is
canceled in this way transitions to the Canceled state, which the
calling code can use to verify that the task responded to its
cancellation request.
I have written a class with a method that runs as a long-running Task in the thread pool. The method is a monitoring service to periodically make a REST request to check on the status of another system. It's just a while() loop with a try()catch() inside so that it can handle its own exceptions and and gracefully continuing if something unexpected happens.
Here's an example:
public void LaunchMonitorThread()
{
Task.Run(() =>
{
while (true)
{
try
{
//Check system status
Thread.Sleep(5000);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
});
}
It works fine, but I want to know if there's another pattern I could use that would allow the Monitor method to run as regular part of a standard Async/Await application, instead of launching it with Task.Run() -- basically I'm trying to avoid fire-and-forget pattern.
So I tried refactoring the code to this:
public async Task LaunchMonitorThread()
{
while (true)
{
try
{
//Check system status
//Use task.delay instead of thread.sleep:
await Task.Delay(5000);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
}
But when I try to call the method in another async method, I get the fun compiler warning:
"Because this call is not awaited, execution of the current method continues before the call is completed."
Now I think this is correct and what I want. But I have doubts because I'm new to async/await. Is this code going to run the way I expect or is it going to DEADLOCK or do something else fatal?
What you are really looking for is the use of a Timer. Use the one in the System.Threading namespace. There is no need to use Task or any other variation thereof (for the code sample you have shown).
private System.Threading.Timer timer;
void StartTimer()
{
timer = new System.Threading.Timer(TimerExecution, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
void TimerExecution(object state)
{
try
{
//Check system status
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
From the documentation
Provides a mechanism for executing a method on a thread pool thread at specified intervals
You could also use System.Timers.Timer but you might not need it. For a comparison between the 2 Timers see also System.Timers.Timer vs System.Threading.Timer.
If you need fire-and-forget operation, it is fine. I'd suggest to improve it with CancellationToken
public async Task LaunchMonitorThread(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
//Check system status
//Use task.delay instead of thread.sleep:
await Task.Delay(5000, token);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
}
besides that, you can use it like
var cancellationToken = new CancellationToken();
var monitorTask = LaunchMonitorThread(cancellationToken);
and save task and/or cancellationToken to interrupt monitor wherever you want
The method Task.Run that you use to fire is perfect to start long-running async functions from a non-async method.
You are right: the forget part is not correct. If for instance your process is going to close, it would be neater if you kindly asked the started thread to finish its task.
The proper way to do this would be to use a CancellationTokenSource. If you order the CancellationTokenSource to Cancel, then all procedures that were started using Tokens from this CancellationTokenSource will stop neatly within reasonable time.
So let's create a class LongRunningTask, that will create a long running Task upon construction and Cancel this task using the CancellationTokenSource upon Dispose().
As both the CancellationTokenSource as the Task implement IDisposable the neat way would be to Dispose these two when the LongRunningTask object is disposed
class LongRunningTask : IDisposable
{
public LongRunningTask(Action<CancellationToken> action)
{ // Starts a Task that will perform the action
this.cancellationTokenSource = new CancellationTokenSource();
this.longRunningTask = Task.Run( () => action (this.cancellationTokenSource.Token));
}
private readonly CancellationTokenSource cancellationTokenSource;
private readonly Task longRunningTask;
private bool isDisposed = false;
public async Task CancelAsync()
{ // cancel the task and wait until the task is completed:
if (this.isDisposed) throw new ObjectDisposedException();
this.cancellationTokenSource.Cancel();
await this.longRunningTask;
}
// for completeness a non-async version:
public void Cancel()
{ // cancel the task and wait until the task is completed:
if (this.isDisposed) throw new ObjectDisposedException();
this.cancellationTokenSource.Cancel();
this.longRunningTask.Wait;
}
}
Add a standard Dispose Pattern
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing && !this.isDisposed)
{ // cancel the task, and wait until task completed:
this.Cancel();
this.IsDisposed = true;
}
}
Usage:
var longRunningTask = new LongRunningTask( (token) => MyFunction(token)
...
// when application closes:
await longRunningTask.CancelAsync(); // not necessary but the neat way to do
longRunningTask.Dispose();
The Action {...} has a CancellationToken as input parameter, your function should regularly check it
async Task MyFunction(CancellationToken token)
{
while (!token.IsCancellationrequested)
{
// do what you have to do, make sure to regularly (every second?) check the token
// when calling other tasks: pass the token
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
}
Instead of checking for Token, you could call token.ThrowIfCancellationRequested. This will throw an exception that you'll have to catch
I have threaded task wich performs some operation in loop:
static void TaskAction(CancellationToken ct)
{
while (SomeCondition())
{
DoSomeSeriousJob();
ct.ThrowIfCancellationRequested();
}
}
static void DoSomeSeriousJob()
{
Console.WriteLine("Serious job started");
Thread.Sleep(5000);
Console.WriteLine("Serious job done");
}
I start it and then cancel after some period of time:
var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => TaskAction(cts.Token), cts.Token);
Thread.Sleep(1000);
cts.Cancel();
This operation must be finished correctly, I don't want to interrupt it. But I want to send a cancellation request to my task and wait until it finishes correctly (by which I mean it gets to some point in code).
I tryed following approaches:
1. Wait(CancellationToken ct)
try
{
task.Wait(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Task cancelled");
}
// Must be joined here.
In this case program returns immediately from Wait(). The task continues to run until ThrowIfCancellationRequested() but if main thread exits the task gets interrupted too.
2. Wait()
try
{
task.Wait();
}
catch (OperationCanceledException)
{
Console.WriteLine("Task cancelled");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Here main thread waits for completion but at the end AggregateException is risen with InnerException = TaskCancelledException (not OperationCancelledException).
3. Check IsCancellationRequested() and no exceptions
static void TaskAction(CancellationToken ct)
{
while (SomeCondition())
{
DoSomeSeriousJob();
if (ct.IsCancellationRequested)
break;
}
}
// ...
task.Wait();
In this case no exceptions are risen but the task gets status RanToCompletion in the end. This is not distiguishable from correct completion when SomeCodition() starts to return false.
All these problem have easy workarounds but I wonder, may be I'm missing something? Could anybody advise me better solution?
If you want to wait for the task to complete (or gets cancelled) synchronously, you can try this:
cts.Cancel();
Task.Run(async () => {
try {
await task;
}
catch (OperationCanceledException ex) {
// ...
}
).Wait();
So that you can directly catch OperationCanceledException instead of catching an AggregateException.
Edit:
Wait(CanecllationToken)
This approach won't work for that purpose.
MSDN statement:
Waits for the Task to complete execution. The wait terminates if a cancellation token is canceled before the task completes.
Wait()
You can use this approach but as you can see, you should expect an AggregateException not OperationCanceledException. It is also specified in documents of the method.
The AggregateException.InnerExceptions collection contains a TaskCanceledException object.
So in this approach, in order to make sure operation is cancelled, you can check if inner expection contains a TaskCanceledException or not.
Check IsCancellationRequested() and no exceptions
In this way, this is obvious that no exception is thrown and you can't find out if the operation is cancelled or not.
If you don't want to wait synchronously, everything works as expected:
cts.Cancel();
try {
await task;
}
catch (OperationCanceledException ex) {
// ...
}
Try this:
try
{
task.GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
Console.WriteLine("Task cancelled");
}
You'll get an OperationCanceledException and it won't be wrapped with AggregateException.
When I call QueueClient.Close(), it always raises an exception:
The operation cannot be performed because the entity has been closed
or aborted.
It raises the exception, even if the queue was empty.
Although I handle it by using OnMessageOptions.ExceptionReceived, but it's annoying me. Is there something wrong with my code?
How to stop the QueueClient gracefully?
[Start, and stop the messaging]
// create a QueueClient with Exception handler.
var queueClient = queueManager.GetStorage<UpdateTriggerQueueClient>();
var options = new OnMessageOptions
{
AutoComplete = false
};
// When the Close() called, it always handles an exception.
options.ExceptionReceived += (sender, args) =>
logger.Error("An excepion occurred.", args.Exception);
// prepare a CancellationTokenSource.
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
// start message pump.
queueClient.OnMessageAsync(async message =>
await DoWork(message, cancellationToken), options);
// sometime after, stop(cancel) the work.
Task.Delay(5000).Wait();
cancellationTokenSource.Cancel();
// some code to wait every in-progress messages finished.
// ...
// close the client.
queueClient.Close();
[DoWork method]
private async Task DoWork(BrokeredMessage message, CancellationToken cancellationToken)
{
logger.Trace("begin work");
// Do something cancellable work.
await Task.Delay(500, cancellationToken)
.ContinueWith(async t =>
{
// complete the message when the task completed,
// otherwise, abandon the message.
if (t.Status == TaskStatus.RanToCompletion)
{
await message.CompleteAsync();
}
else
{
await message.AbandonAsync();
}
})
.ContinueWith(t =>
{
// cleanup
logger.Trace("end work");
});
}
You can stop all messages from being recieved by updating the subscription with:
_namespaceManager.UpdateSubscription(new SubscriptionDescription(_strTopic, _strSubscription)
{
Status = EntityStatus.ReceiveDisabled
});
This may solve your problem even though it's not exactly what you're asking. (I'm also trying to find out how to close() properly.
You will also need to update the status to begin receiving again.
I think it is important to understand how the IQueueClient interface works. When you receive a message you receive what is called a CancellationToken. The CancellationToken enables you to cooperate a cancellation between threads, thread pool work items, or Task object. In this case you need to handle Azure Service Bus connection. This is what I will do, control if the connection has been closed, if that is the case, I will check if the message we have received still alive.
public async Task<object> ReceiveAsync(Message message, CancellationToken token)
{
//If the connection still open, we do something
if (!token.IsCancellationRequested)
{
...
}
}
I read on https://msdn.microsoft.com/en-us/library/azure/hh528527.aspx that close should not be called as the connection is managed by the message factories.
this.mSubscriptionClient.OnMessage(
(pMessage) =>
{
try
{
// Will block the current thread if Stop is called.
this.mPauseProcessingEvent.WaitOne();
// Execute processing task here
pProcessMessageTask(pMessage);
}
catch(Exception ex)
{
this.RaiseOnLogMessageEvent(pMessage, ex);
}
},
options);
And then when you are ready to stop
this.mPauseProcessingEvent.Reset();
mSubscriptionClient.Close();
The datatype is
private ManualResetEvent mPauseProcessingEvent;