Cancel and reactivate async task - c#

I want to create a statusbar. The status should be set from any method inside the class. If the status is set it should be for 5000ms visible. After 5000ms the status should be empty. Sometimes it can happen, that I want to set a status when an old status is still active. For this case the old status should be overwritten and the await Task.Delay(5000); should be reset and start counting from 0.
My current code looks like this:
public CancellationTokenSource tokenSource { get; set; }
public CancellationToken token { get; set; }
public async Task SetStatusMessage(string pStatusMessage)
{
tokenSource.Cancel();
await Task.Run(async () =>
{
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
this.Dispatcher.Invoke(() =>
{
this.txtStatusMessage.Text = pStatusMessage;
});
await Task.Delay(5000, token);
this.Dispatcher.Invoke(() =>
{
this.txtStatusMessage.Text = "";
});
}, token);
public async void AnyMethod()
{
await this.SetStatusMessage("Hello World");
}
This isn't working, because I cancel the task before it is running. That's why I get an OperationCanceledException (?).

This is what I would do. I have no idea why you use Task.Run() when you are using async.
public async Task SetStatusMessage(string pStatusMessage)
{
CancellationTokenSource localToken;
try
{
if (tokenSource != null)
tokenSource.Cancel();
tokenSource = new CancellationTokenSource();
localToken = this.tokenSource;
this.txtStatusMessage.Text = pStatusMessage;
await Task.Delay(5000, localToken.token);
this.txtStatusMessage.Text = "";
}
catch (TaskCanceledException) {}
finally
{
localToken.Dispose();
}
}

Related

Restricting SendAsync calls to 5 messages per second

I'm implementing Binance's API.
The documentation says:
WebSocket connections have a limit of 5 incoming messages per second. A message is considered:
A PING frame
A PONG frame
A JSON controlled message (e.g. subscribe, unsubscribe)
For ex. there is a simple web socket wrapper such as the one from the official Binance Connector. According to the limitation above, SendAsync should be restricted 5 messages per second. If a few threads call SendAsync 5 times at the same time (including PING frame which is built-in the ClientWebSocket class), it's going to fail. How can I solve the issue with that limitation gracefully? Using bounded channels is a solution?
public class BinanceWebSocket : IDisposable
{
private IBinanceWebSocketHandler handler;
private List<Func<string, Task>> onMessageReceivedFunctions;
private List<CancellationTokenRegistration> onMessageReceivedCancellationTokenRegistrations;
private CancellationTokenSource loopCancellationTokenSource;
private Uri url;
private int receiveBufferSize;
public BinanceWebSocket(IBinanceWebSocketHandler handler, string url, int receiveBufferSize = 8192)
{
this.handler = handler;
this.url = new Uri(url);
this.receiveBufferSize = receiveBufferSize;
this.onMessageReceivedFunctions = new List<Func<string, Task>>();
this.onMessageReceivedCancellationTokenRegistrations = new List<CancellationTokenRegistration>();
}
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (this.handler.State != WebSocketState.Open)
{
this.loopCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
await this.handler.ConnectAsync(this.url, cancellationToken);
await Task.Factory.StartNew(() => this.ReceiveLoop(loopCancellationTokenSource.Token, this.receiveBufferSize), loopCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
public async Task DisconnectAsync(CancellationToken cancellationToken)
{
if (this.loopCancellationTokenSource != null)
{
this.loopCancellationTokenSource.Cancel();
}
if (this.handler.State == WebSocketState.Open)
{
await this.handler.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, cancellationToken);
await this.handler.CloseAsync(WebSocketCloseStatus.NormalClosure, null, cancellationToken);
}
}
public void OnMessageReceived(Func<string, Task> onMessageReceived, CancellationToken cancellationToken)
{
this.onMessageReceivedFunctions.Add(onMessageReceived);
if (cancellationToken != CancellationToken.None)
{
var reg = cancellationToken.Register(() =>
this.onMessageReceivedFunctions.Remove(onMessageReceived));
this.onMessageReceivedCancellationTokenRegistrations.Add(reg);
}
}
public async Task SendAsync(string message, CancellationToken cancellationToken)
{
byte[] byteArray = Encoding.ASCII.GetBytes(message);
await this.handler.SendAsync(new ArraySegment<byte>(byteArray), WebSocketMessageType.Text, true, cancellationToken);
}
public void Dispose()
{
this.DisconnectAsync(CancellationToken.None).Wait();
this.handler.Dispose();
this.onMessageReceivedCancellationTokenRegistrations.ForEach(ct => ct.Dispose());
this.loopCancellationTokenSource.Dispose();
}
private async Task ReceiveLoop(CancellationToken cancellationToken, int receiveBufferSize = 8192)
{
WebSocketReceiveResult receiveResult = null;
try
{
while (!cancellationToken.IsCancellationRequested)
{
var buffer = new ArraySegment<byte>(new byte[receiveBufferSize]);
receiveResult = await this.handler.ReceiveAsync(buffer, cancellationToken);
if (receiveResult.MessageType == WebSocketMessageType.Close)
{
break;
}
string content = Encoding.UTF8.GetString(buffer.ToArray());
this.onMessageReceivedFunctions.ForEach(omrf => omrf(content));
}
}
catch (TaskCanceledException)
{
await this.DisconnectAsync(CancellationToken.None);
}
}
}
Second way which I'm not 100% sure it solves it
SendAsync is being called in a loop using Channels. SingleReader is set to true, which means there will be only one consumer at a time. It technically should solve the issue, but I'm not 100% sure because the channel might only be limiting the amount in the buffer.
private readonly Channel<string> _messagesTextToSendQueue = Channel.CreateUnbounded<string>(new UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = false
});
public ValueTask SendAsync(string message)
{
Validations.Validations.ValidateInput(message, nameof(message));
return _messagesTextToSendQueue.Writer.WriteAsync(message);
}
public void Send(string message)
{
Validations.Validations.ValidateInput(message, nameof(message));
_messagesTextToSendQueue.Writer.TryWrite(message);
}
private async Task SendTextFromQueue()
{
try
{
while (await _messagesTextToSendQueue.Reader.WaitToReadAsync())
{
while (_messagesTextToSendQueue.Reader.TryRead(out var message))
{
try
{
await SendInternalSynchronized(message).ConfigureAwait(false);
}
catch (Exception e)
{
Logger.Error(e, L($"Failed to send text message: '{message}'. Error: {e.Message}"));
}
}
}
}
catch (TaskCanceledException)
{
// task was canceled, ignore
}
catch (OperationCanceledException)
{
// operation was canceled, ignore
}
catch (Exception e)
{
if (_cancellationTotal.IsCancellationRequested || _disposing)
{
// disposing/canceling, do nothing and exit
return;
}
Logger.Trace(L($"Sending text thread failed, error: {e.Message}. Creating a new sending thread."));
StartBackgroundThreadForSendingText();
}
}
I would try to keep it as simple as possible and use Semaphore Slim to achieve this, I have created a class to perform this task.
public class ThrottlingLimiter
{
private readonly SemaphoreSlim _semaphore;
private readonly TimeSpan _timeUnit;
public ThrottlingLimiter(int maxActionsPerTimeUnit, TimeSpan timeUnit)
{
if (maxActionsPerTimeUnit < 1)
throw new ArgumentOutOfRangeException(nameof(maxActionsPerTimeUnit));
if (timeUnit < TimeSpan.Zero || timeUnit.TotalMilliseconds > int.MaxValue)
throw new ArgumentOutOfRangeException(nameof(timeUnit));
_semaphore = new SemaphoreSlim(maxActionsPerTimeUnit, maxActionsPerTimeUnit);
_timeUnit = timeUnit;
}
public async Task WaitAsync(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
ScheduleSemaphoreRelease();
}
private async void ScheduleSemaphoreRelease()
{
await Task.Delay(_timeUnit).ConfigureAwait(false);
_semaphore.Release();
}
}
Now to Use this class, all you have to do is set your limit and timeSpan
public async Task SendData(List<string> allMessages)
{
// Limiting 5 calls per second
ThrottlingLimiter throttlingLimiter = new ThrottlingLimiter(5, TimeSpan.FromSeconds(1));
await Task.WhenAll(allMessages.Select(async message =>
{
await throttlingLimiter.WaitAsync();
try {
await SendInternalSynchronized(message);
// I am not sure what this SendInternalSynchronized returns but I would return some thing to keep a track if this call is successful or not
}
catch (Exception e)
{
Logger.Error(e, L($"Failed to send text message: {message}'. Error: {e.Message}"));
}
});
}
so basically what will happen here is, no matter how big your list is, the ThrottlingLimiter will only send 5 messages per second and wait for the next second to send the next 5 messages.
so, in your case, get all the data from your call to
await _messagesTextToSendQueue.Reader.WaitToReadAsync();
store that into a list or any collection and pass that to the SendData function.

Cancel Async Task with CancellationTokenSource from a button click not working

What I need to do is able to cancel a task that is running async. Task should be cancelled on cancel button click. I have done it with CancellationTokenSource. But it is not working properly.
public class classA
{
CancellationTokenSource _tokenSource = null;
public void OnCancelButtonClick()
{
MessageBox.Show("Do you Really want to cancel upload");
_tokenSource = new CancellationTokenSource();
_tokenSource.Cancel();
}
public async void UploadBtnClick(object param )
{
_tokenSource = new CancellationTokenSource();
var token = _tokenSource.Token;
try
{
await Task.Run(() => UploadFunction(token));
}
catch(OperationCanceledException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
_tokenSource.Dispose();
}
}
public Task<bool> UploadFunction(CancellationToken token)
{
foreach
{
//code here
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
}
}
}
I am calling OnCancelButtonClick from another function
public class classB
{
public void CancelUploadBtnExecute(object param)
{
FilesViewModel vm = new FilesViewModel();
vm.OnCancelButtonClick();
}
}
when I click on OnCancelButtonClick , IsCancellationRequested is false, but not reflected inside UploadFunction so the task won't stop.
It's because you are initializing new CancellationTokenSource in OnCancelButtonClick().
Just remove this line from OnCancelButtonClick()
_tokenSource = new CancellationTokenSource();
UploadFunction method is using token created from CancellationTokenSource in UploadBtnClick method.

How to properly cancel Task and catch OperationCanceledException?

This question has already been asked but I still don't get it... I'm trying to cancel a task but when I include ThrowIfCancellationRequested(), it is not catched and if I don't include it, the GUI freezes...
Here is my code that run without the ThrowIfCancellationRequested() but freeze the GUI:
public void StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
processThread = Task.Factory.StartNew(() =>
{
while (true)
{
//Do some work with myvariable
if (token.IsCancellationRequested)
{
break;
}
}
}, token);
}
And the code with the ThrowIfCancellationRequested() that is not catched, the debugger stops on the line token.ThrowIfCancellationRequested():
public void StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken tokenDispatcher = tokenSource.Token;
processThread = Task.Factory.StartNew(() =>
{
try
{
while (true)
{
//Do some work with myvariable
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
break;
}
}
}
catch (AggregateException ae)
{
if (ae.InnerException is OperationCanceledException)
{
Console.WriteLine("Process Thread Cancelled");
}
else
{
Console.WriteLine("ERROR");
}
}
}, token);
}
What am I doing wrong?
The ThrowIfCancellationRequested is the proper way to cancel the task. What you need to understand is that there will be no exception when you cancel a Task. Only the Status of the task will be set to Canceled. The try catch block you have inside the task will do nothing.
To get an exception you need to await the Task in an async method or use the Wait method to wait for the finish of the task. Using the Wait method is not recommended because you are blocking the current thread until the task finishes.
Here is a suggestion how you could implement this from your code.
public static Task StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
return Task.Factory.StartNew(() =>
{
while (true)
{
//Do some work with myvariable
token.ThrowIfCancellationRequested();
}
}, token);
}
public async Task ExecuteProcess(double myvariable)
{
try
{
await StartProcess(myvariable);
}
catch (OperationCanceledException ex)
{
Console.WriteLine("Process Thread Canceled");
}
catch (Exception ex)
{
Console.WriteLine("ERROR");
}
}
The ExecuteProcess method demonstrates how you need to call your function to get the exception. This way you also prevent the GUI from blocking if you call the method from the UI thread.
I recommend you read the documentation on asynchronous programming to get more understanding on how it works.

Refactor to remove Taks.Run from my code

I just wrote the following code
public void Save()
{
while (this.IsAsyncInProcess)
Thread.Sleep(100);
this.customer.OrderCount = this.orders.Count();
this.customer.OrderTotal = this.orders.Sum(o => x.Total);
this.customerRepo.Save();
}
public async Task LoadAsync()
{
this.IsAsyncInProcess = true;
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
this.IsAsyncInProcess = false;
}
Now I had a classical deadlock because, after this.orders completed, it would wait for the gui thread to resume in order to set this.IsAsyncInProcess to false. However, the gui thread was busy inside Save()
Now I refactored LoadAsync to
public async Task LoadAsync()
{
await Task.Run(async () =>
{
this.IsAsyncInProcess = true;
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
this.IsAsyncInProcess = false;
});
}
I can't just refactor Save to SaveAsync for compability reasons.
Is there a better way to achive this, without using Task.Run?
You can use async locking. If you can't change the signiture of Save then you can just proxy to an async local function.
static SemaphoreSlim sem = new SemaphoreSlim(1,1);
public void Save()
{
SaveAsync();
public async Task SaveAsync()
{
await sem.WaitAsync();
try{
this.customer.OrderCount = this.orders.Count();
this.customer.OrderTotal = this.orders.Sum(o => x.Total);
this.customerRepo.Save();
}finally{
sem.Release();
}
}
}
public async Task LoadAsync()
{
await sem.WaitAsync();
try{
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
}finally{
sem.Release();
}
}
or even better create your own async lock
static SemaphoreSlim sem = new SemaphoreSlim(1,1);
public static async Task<IDisposable> LockAsync(){
await sem.WaitAsync();
return Disposable.Create(()=>sem.Release());
}
public void Save()
{
SaveAsync();
public async Task SaveAsync()
{
using(await LockAsync()){
this.customer.OrderCount = this.orders.Count();
this.customer.OrderTotal = this.orders.Sum(o => x.Total);
this.customerRepo.Save();
}
}
}
public async Task LoadAsync()
{
using(await LockAsync()){
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
}
}

Correct use of CancellationToken

This is my situation:
private CancellationTokenSource cancellationTokenSource;
private CancellationToken cancellationToken;
public IoTHub()
{
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
receive();
}
private void receive()
{
eventHubClient = EventHubClient.CreateFromConnectionString(connectionString, iotHubD2cEndpoint);
var d2cPartitions = eventHubClient.GetRuntimeInformation().PartitionIds;
foreach (string partition in d2cPartitions)
{
ReceiveMessagesFromDeviceAsync(partition, cancellationToken);
}
}
private async Task ReceiveMessagesFromDeviceAsync(CancellationToken ct)
{
var eventHubReceiver = eventHubClient.GetDefaultConsumerGroup().CreateReceiver(partition, DateTime.UtcNow);
while (true)
{
if(ct.IsCancellationRequested)
{
break;
}
EventData eventData = await eventHubReceiver.ReceiveAsync();
if (eventData == null) continue;
string data = Encoding.UTF8.GetString(eventData.GetBytes());
// Javascript function with Websocket
Clients.All.setMessage(data);
}
}
public void cancelToken()
{
cancellationTokenSource.Cancel();
}
The Task will not be cancelled, when calling the cancelToken method. How come?
I have read the Microsoft guide, an other Stackoverflow questions about Task cancellation.
But still have difficulty using them correctly.
You can consider CancellationToken like a flag, indicating if a cancellation signal is received. Thus:
while (true)
{
//you check the "flag" here, to see if the operation is cancelled, correct usage
if(ct.IsCancellationRequested)
{
break;
}
//your instance of CancellationToken (ct) can't stop this task from running
await LongRunningTask();
}
If you want LongRunningTask to be cancelled, you should use CancellationToken inside the task body and check it when necessary, like this:
async Task LongRunningTask()
{
await DoPrepareWorkAsync();
if (ct.IsCancellationRequested)
{
//it's cancelled!
return;
}
//let's do it
await DoItAsync();
if (ct.IsCancellationRequested)
{
//oh, it's cancelled after we already did something!
//fortunately we have rollback function
await RollbackAsync();
}
}

Categories