How could I cancel task with different threads? - c#

I'm writing an own TelegramBot. In that Bot there is a function subscribe/unsubscribe. When user starts "Subscribtion" TelegramBot should send a message "1111" each three seconds. But after unsubscription message keep sending. Could someone help me with that issue?
Method for start subscription:
private async Task OnStartSubscribeAsync(string userName, long userId,
ITelegramBotClient _client, long chatId)
{
var user = new UserDTO
{
UserId = userId.ToString(),
UserName = userName
};
await _userService.StartSubscribeAsync(user);
await _client.SendTextMessageAsync(chatId, "You subscribed successfully ");
try
{
await CheckTick(_client, chatId);
}
catch (OperationCanceledException e)
{
await _client.SendTextMessageAsync(chatId, "STOPPED");
}
finally
{
tokenSource.Dispose();
}
var articles = await ReturnNewArticles();
foreach (var item in articles)
{
var linkButton = KeyboardGoOver("Перейти", (EncodeUrl(item.Href)));
await _client.SendPhotoAsync(chatId: chatId, photo: item.Image,
caption: $"*{item.Title}*",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
replyMarkup: linkButton);
}
}
Method for sending message with delay:
private Task CheckTick(ITelegramBotClient _client, long chatId)
{
return Task.Run(async () =>
{
tokenSource.Token.ThrowIfCancellationRequested();
while (true)
{
await Task.Delay(3000);
await _client.SendTextMessageAsync(chatId, "1111");
if (tokenSource.Token.IsCancellationRequested)
{
tokenSource.Token.ThrowIfCancellationRequested();
}
}
}, tokenSource.Token);
}
Method for unsubscribe:
private async Task OnStopSubscibeAsync(string userName, long userId,
ITelegramBotClient _client, long chatId)
{
var user = new UserDTO()
{
UserId = userId.ToString(),
UserName = userName
};
await _userService.StopSubscribeAsync(user);
tokenSource.Cancel();
await _client.SendTextMessageAsync(chatId, "You unsubscribed successfully");
}
Definition of tokenSource:
private CancellationTokenSource tokenSource = new();
I think there are some issues with CancelationToken with threads. When I tried to debug, I didn't hit to block "catch".

Related

CancellationToken in Web API not working correctly

I have Web Api which gets CancellationToken from users, the method inside it (DoWork) also get CancellationToken:
[HttpPost]
public async Task<long> GetInfo(CancellationToken cancellationToken)
{
long result = 0;
bool notDone = true;
Task<long> t = Task.Run(async () =>
{
if (cancellationToken.IsCancellationRequested)
cancellationToken.ThrowIfCancellationRequested();
while (notDone && !cancellationToken.IsCancellationRequested)
{
result = await DoWork(cancellationToken);
notDone = false;
}
return result;
}, cancellationToken);
try
{
return await t;
}
catch (AggregateException e)
{
Debug.WriteLine("Exception messages:");
foreach (var ie in e.InnerExceptions)
Debug.WriteLine(" {0}: {1}", ie.GetType().Name, ie.Message);
Debug.WriteLine("\nTask status: {0}", t.Status);
throw;
}
catch (Exception ex)
{
throw;
}
}
private Task<long> DoWork(CancellationToken token)
{
long result = 0;
bool notDone = true;
Task<long> task = Task.Run(() =>
{
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
while (notDone && !token.IsCancellationRequested)
{
Thread.Sleep(8000);
result = 2;
notDone = false;
}
return result;
}, token);
return task;
}
I expect when the user cancels the request it aborts the DoWork method and not continue the function, but after sending an Exception, when "Thread.Sleep" complete, the DoWork method continue.
the user can call the API service like this method "cc" as you can see it cancel after 5 seconds and in the DoWork method "Thread.Sleep" is 9 seconds. the user gets an Exception but the method still running.
private async Task<bool> cc()
{
UriBuilder builder = new UriBuilder("http://localhost:12458/api/Test/GetInfo");
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
HttpClient client = new HttpClient();
System.Threading.CancellationTokenSource s = new System.Threading.CancellationTokenSource();
s.CancelAfter(5000);
try
{
var result = client.PostAsJsonAsync<model1>(builder.ToString(), new model1 { }, s.Token).Result;
string tmp = result.Content.ReadAsStringAsync().Result;
long ApiResult = JsonConvert.DeserializeObject<long>(tmp);
}
catch (TaskCanceledException ex)
{
}
catch (OperationCanceledException ex)
{
}
catch (Exception ex)
{
}
finally
{
s.Dispose();
}
return false;
}
When use Thread.Sleep(8000) actually hold the main thread for 8 seconds and It can't check the cancellation token. you should use Task.Delay with cancellation token like this:
while (notDone && !token.IsCancellationRequested)
{
await Task.Delay(8000, token);
result = 2;
notDone = false;
}
Task.Delay check the cancellation token itself.
Ok, in your code you should process CancellationToken.IsCancellationRequested too. It's not kinda of magic, you should do this work.
public void DoWork(CancellationToken ctsToken)
{
ctsToken.ThrowIfCancellationRequested();
DoSomething();
ctsToken.ThrowIfCancellationRequested();
DoSomethingElse();
// end so on with checking CancellationToken before every part of work
}
And your Task should look like this
Task<long> t = Task.Run(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
result = await DoWork(cancellationToken);
notDone = false;
cancellationToken.ThrowIfCancellationRequested();
return result;
}, cancellationToken);
In my case it was because of fiddler. When I closed the fiddler app, it started working like a charm.

Correct way of starting a forever-running loop with Async

I've an existing code I wrote some time ago, that works but I dislike the fact that the thread I start remains in loop.
This piece of code is a consumer on an IBMMQ code, waiting for messages to be processed.The problem I've is that with the following code
private Task ExecuteQueuePolling(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
ConnectToAccessQueue();
Logger.Debug($"Accessed to the queue {queueName}");
Logger.DebugFormat("Repeating timer started, checking frequency: {checkingFrequency}",
checkingFrequency);
while (!cancellationToken.IsCancellationRequested)
{
Logger.Trace( () => "Listening on queues for new messages");
// isChecking = true;
var mqMsg = new MQMessage();
var mqGetMsgOpts = new MQGetMessageOptions
{ WaitInterval = (int)checkingFrequency.TotalMilliseconds };
// 15 second limit for waiting
mqGetMsgOpts.Options |= MQC.MQGMO_WAIT | MQC.MQGMO_FAIL_IF_QUIESCING |
MQC.MQCNO_RECONNECT_Q_MGR | MQC.MQOO_INPUT_AS_Q_DEF;
try
{
mqQueue.Get(mqMsg, mqGetMsgOpts);
if (string.Compare(mqMsg.Format, MQC.MQFMT_STRING, StringComparison.Ordinal) == 0)
{
var text = mqMsg.ReadString(mqMsg.MessageLength);
Logger.Debug($"Message received : [{text}]");
Message message = new Message { Content = text };
foreach (var observer in observers)
observer.OnNext(message);
}
else
{
Logger.Warn("Non-text message");
}
}
catch (MQException ex)
{
if (ex.Message == MQC.MQRC_NO_MSG_AVAILABLE.ToString())
{
Logger.Trace("No messages available");
//nothing to do, emtpy queue
}
else if (ex.Message == MQC.MQRC_CONNECTION_BROKEN.ToString())
{
Logger.ErrorException("MQ Exception, trying to recconect", ex);
throw new ReconnectException();
}
}
Thread.Sleep(100);
}
},cancellationToken);
}
//Calling method
try
{
string queueManagerName = configuration.GetValue<string>("IBMMQ:QUEUE_MANAGER_NAME");
// var queueManager = new MQQueueManager(queueManagerName,dictionary2);
QueueMonitor monitor = new QueueMonitor(configuration, "IMPORTER_RECEIVER_TEST");
//_subscription = monitor.Subscribe(receiver);
await monitor.StartAsync(cts.Token).ConfigureAwait(false);
}
catch (Exception e)
{
log.Error(e, "Error creating the queue monitor or it's subscription");
}
finally
{
WaitForCancel(cts);
}
The call to await monitor.StartAsync(cts.Token).ConfigureAwait(false); remains pending.
How should I modify my code, so that the call returns and in background the task continue to loop?
Thanks in advance
Here is how you can simplify your code by replacing Thread.Sleep with Task.Delay:
private async Task ExecuteQueuePolling(CancellationToken cancellationToken)
{
while (true)
{
// Process mqQueue here
await Task.Delay(100, cancellationToken);
}
}
Task.Delay has the advantage that accepts a CancellationToken, so in case of cancellation the loop will exit immediately. This could be important if the pooling of the MQ was lazier (for example every 5 seconds).
private static Task _runningTask;
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
_runningTask = ExecuteQueuePolling(cts.Token);
WaitForCancel(cts);
}
private static void WaitForCancel(CancellationTokenSource cts)
{
var spinner = new SpinWait();
while (!cts.IsCancellationRequested
&& _runningTask.Status == TaskStatus.Running) spinner.SpinOnce();
}
private static Task ExecuteQueuePolling(CancellationToken cancellationToken)
{
var t = new Task(() =>
{
while (!cancellationToken.IsCancellationRequested)
; // your code
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException();
}, cancellationToken, TaskCreationOptions.LongRunning);
t.Start();
return t;
}

c# async update http then save to local db WhenAll ContinueWith timing

I've got the following code which seems to run fine apart from the continuation on the WhenAll ... await Task.WhenAll(syncTasks).ContinueWith ... is run before all four methods are completed. Would appreciate any guidance on what I'm doing wrong here. I don't really feel like I understand how to arrange complex async functionality and what seems to be happening supports that. This is in a Xamarin App BTW although I don't suppose that really matters.
private async Task SyncItems()
{
var updateItemOnes = Task.Run(() =>
{
UpdateItemOnesToServer(itemOnesToUpdate).ContinueWith(async (result) => {
if (!result.IsFaulted && !result.IsCanceled)
{
await UpdateItemOnesToLocal(itemOnesToUpdate);
}
});
});
syncTasks.Add(updateItemOnes);
var updateItemTwos = Task.Run(() =>
{
UpdateItemTwosToServer(itemTwosToUpdate).ContinueWith(async (result) => {
if (!result.IsFaulted && !result.IsCanceled)
{
await UpdateItemTwosToLocal(itemTwosToUpdate);
}
});
});
syncTasks.Add(updateItemTwos );
//Show Loading Dialog
await Task.WhenAll(syncTasks).ContinueWith((result) => {
if (!result.IsFaulted && !result.IsCanceled)
{
//Success
}
else
{
//Error
}
//Hide Loading Dialog
});
}
private async Task UpdateItemOnesToServer(IEnumerable<Item> itemOnesToUpdate)
{
try
{
var listofTasks = new List<Task>();
foreach (var item in itemOnesToUpdate)
{
var convertItemOneTask = Task.Run(async () => {
//Convert Image File in Item to Base64 here
});
listofTasks.Add(convertItemOneTask);
}
await Task.WhenAll(listofTasks);
var response = await _apiManager.SaveItemOnes(itemOnesToUpdate);
if (response.IsSuccessStatusCode)
{
//Update ItemOnes for Local Update with Response Values
}
}
catch
{
throw;
}
}
private async Task UpdateItemOnesToLocal(IEnumerable<Item> itemOnesToUpdate)
{
var listOfTasks = new List<Task<bool>>();
foreach (var itemOne in itemOnesToUpdate)
{
listOfTasks.Add(_localService.UpdateItemOne(itemOne));
}
await Task.WhenAll<bool>(listOfTasks);
}
private async Task UpdateItemTwosToServer(IEnumerable<ItemOne> itemTwosToUpdate)
{
try
{
var listofTasks = new List<Task>();
foreach (var item in itemTwosToUpdate)
{
var convertItemTwoTask = Task.Run(async () => {
//Convert Image File in Item to Base64 here
});
listofTasks.Add(convertItemTwoTask);
}
await Task.WhenAll(listofTasks);
var response = await _apiManager.SaveItemTwos(itemTwosToUpdate);
if (response.IsSuccessStatusCode)
{
//Update ItemTwos for Local Update with Response Values
}
}
catch
{
throw;
}
}
private async Task UpdateItemTwosToLocal(IEnumerable<ItemTwo> itemTwosToUpdate)
{
var listOfTasks = new List<Task<bool>>();
foreach (var itemTwo in itemTwosToUpdate)
{
listOfTasks.Add(_localService.UpdateItemTwo(itemTwo));
}
await Task.WhenAll<bool>(listOfTasks);
}
Thanks in advance to anyone who can provide a little clarity. It will be much appreciated.
So there are a few problems with this code.
someTask.ContinueWith(X)
Basically this says "once the someTask is completed, do X" (there's more going on but in this case think of it like this). However if you await someTask this will not include the ContinueWith part. So like this the Task.WhenAll(syncTasks) will not wait on your ContinueWith parts.
var updateItemOnes = Task.Run(() => UpdateItemOnesToServer()) wrappers. There is no awaiting here, so this will create a Task that just starts the UpdateItemOnesToServer task. That is done instantly.
If you would like to see what is happening in practice use this test class:
class TestAsyncClass
{
public async Task Run()
{
var tasks = new List<Task>();
Console.WriteLine("starting tasks");
var task1 = Task.Run(() => {
FakeServerCall1().ContinueWith(async (result) =>
{
if (!result.IsFaulted && !result.IsCanceled)
await FakeLocalCall1();
});
});
tasks.Add(task1);
var task2 = Task.Run(() => {
FakeServerCall2().ContinueWith(async (result) =>
{
if (result.IsCompletedSuccessfully)
await FakeLocalCall2();
});
});
tasks.Add(task2);
Console.WriteLine("starting tasks completed");
await Task.WhenAll(tasks);
Console.WriteLine("tasks completed");
}
public async Task<bool> FakeServerCall1()
{
Console.WriteLine("Server1 started");
await Task.Delay(3000);
Console.WriteLine("Server1 completed");
return true;
}
public async Task<bool> FakeServerCall2()
{
Console.WriteLine("Server2 started");
await Task.Delay(2000);
Console.WriteLine("Server2 completed");
return true;
}
public async Task<bool> FakeLocalCall1()
{
Console.WriteLine("Local1 started");
await Task.Delay(1500);
Console.WriteLine("Local1 completed");
return true;
}
public async Task<bool> FakeLocalCall2()
{
Console.WriteLine("Local2 started");
await Task.Delay(2000);
Console.WriteLine("Local2 completed");
return true;
}
}
You'll see that the output is as follows:
starting tasks
starting tasks completed
Server1 started
Server2 started
tasks completed
Server2 completed
Local2 started
Server1 completed
Local1 started
Local2 completed
Local1 completed
Notice here the "tasks completed" is called straight after starting the two tasks.
Now if we change the Run method like this I think we'll get the functionality you're looking for:
public async Task Run()
{
var tasks = new List<Task>();
Console.WriteLine("starting tasks");
var task1 = Task.Run(async () =>
{
await FakeServerCall1();
await FakeLocalCall1();
});
tasks.Add(task1);
var task2 = Task.Run(async() =>
{
await FakeServerCall2();
await FakeLocalCall2();
});
tasks.Add(task2);
Console.WriteLine("starting tasks completed");
await Task.WhenAll(tasks);
Console.WriteLine("tasks completed");
}
Which will output:
starting tasks
starting tasks completed
Server1 started
Server2 started
Server2 completed
Local2 started
Server1 completed
Local1 started
Local2 completed
Local1 completed
tasks completed
So we see here that Local1 is always after Server1 and Local2 is always after Server2 and "tasks completed" is always after Local1 & Local2
Hope this helps!
Edit:
From you comment you said you would like to see any exceptions that occurred in the process. This is where you could use ContinueWith (it is also fired when exceptions are throw:
await Task.WhenAll(tasks).ContinueWith((result) =>
{
if (result.IsFaulted)
{
foreach (var e in result.Exception.InnerExceptions)
{
Console.WriteLine(e);
}
}
});
If you change the following test calls:
public async Task<bool> FakeServerCall2()
{
Console.WriteLine("Server2 started");
await Task.Delay(1000);
Console.WriteLine("Crashing Server2");
throw new Exception("Oops server 2 crashed");
}
public async Task<bool> FakeLocalCall1()
{
Console.WriteLine("Local1 started");
await Task.Delay(1500);
Console.WriteLine("crashing local1");
throw new Exception("Oh ohh, local1 crashed");
}
This will be your output:
starting tasks
starting tasks completed
Server1 started
Server2 started
Crashing Server2
Server1 completed
Local1 started
crashing local1
System.Exception: Oh ohh, local1 crashed
at TestConsoleApp.TestAsyncClass.FakeLocalCall1() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 67
at TestConsoleApp.TestAsyncClass.b__0_0() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 17
System.Exception: Oops server 2 crashed
at TestConsoleApp.TestAsyncClass.FakeServerCall2() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 59
at TestConsoleApp.TestAsyncClass.b__0_1() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 23
tasks completed

Messages are not getting delayed in Botframework

I'm posting two messages back to the user as a reply as below,
static Timer t = new Timer(new TimerCallback(TimerEvent));
static Timer t1 = new Timer(new TimerCallback(TimerEventInActivity));
static int timeOut = Convert.ToInt32(ConfigurationManager.AppSettings["disableEndConversationTimer"]); //3600000
public static void CallTimer(int due) {
t.Change(due, Timeout.Infinite);
}
public static void CallTimerInActivity(int due) {
t1.Change(due, Timeout.Infinite);
}
public async static Task PostAsyncWithDelay(this IDialogContext ob, string text) {
try {
var message = ob.MakeMessage();
message.Type = Microsoft.Bot.Connector.ActivityTypes.Message;
message.Text = text;
await PostAsyncWithDelay(ob, message);
CallTimer(300000);
if ("true".Equals(ConfigurationManager.AppSettings["disableEndConversation"])) {
CallTimerInActivity(timeOut);
}
} catch (Exception ex) {
Trace.TraceInformation(ex.Message);
}
}
await context.PostAsyncWithDelay("Great!");
await context.PostAsyncWithDelay("I can help you with that.");
But, there is no delay between them when received. Both messages are received in one go.
How can I delay the second message with some time?
In Root Dialog
To delay your message you can use Task.Delay method. Change your PostAsyncWithDelay as:
public async static Task PostAsyncWithDelay(IDialogContext context, string text)
{
await Task.Delay(4000).ContinueWith(t =>
{
var message = context.MakeMessage();
message.Text = text;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var client = scope.Resolve<IConnectorClient>();
client.Conversations.ReplyToActivityAsync((Activity)message);
}
});
}
You can call PostAsyncWithDelay method when you want to delay a message, otherwise use context.PostAsync method to send your messages.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
//Sending a message nomally
await context.PostAsync("Hi");
//Notify the user that the bot is typing
var typing = context.MakeMessage();
typing.Type = ActivityTypes.Typing;
await context.PostAsync(typing);
//The message you want to delay.
//NOTE: Do not use context.PostAsyncWithDelay instead simply call the method.
await PostAsyncWithDelay(context, "2nd Hi");
}
OUTPUT
How can I delay the second message with some time?
If you’d like to delay sending the second message, you can try the following code snippet:
await context.PostAsync($"You sent {activity.Text} at {DateTime.Now}");
Task.Delay(5000).ContinueWith(t =>
{
using (var scope = Microsoft.Bot.Builder.Dialogs.Internals.DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var client = scope.Resolve<IConnectorClient>();
Activity reply = activity.CreateReply($"I can help you with that..");
client.Conversations.ReplyToActivityAsync(reply);
}
});
context.Wait(MessageReceivedAsync);
Besides, as others mentioned in comments, the method PostAsyncWithDelay seems not a built-in method in Bot Builder SDK. If you try to achieve the requirement and defined that custom method, you can post the code of that method.
To make all replies delay, you may insert this directly in the controller.
if (activity.Type == ActivityTypes.Message)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isTypingReply = activity.CreateReply();
isTypingReply.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isTypingReply);
var message = isTypingReply;
await Task.Delay(4000).ContinueWith(t =>
{
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
}
});
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}

How the async Task return conditionally

Hi am trying to return for an async method task conditionally. Below is the way I tried.
public string DoMessage(MyObj obj)
{
string returnStatus = "Processing...";
var storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
var queueClient = storageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference(ConfigurationManager.AppSettings["QueueName"]);
if (queue.CreateIfNotExists()) {
}
var msg = CloudQueueMessageExtensions.Serialize(obj);
queue.AddMessage(msg);
//Task processTask = RunMessageProces();
var t = Task.Run(() => RunMessageProces());
t.Wait();
return returnStatus;
}
private async Task<string> RunMessageProces()
{
statusProcess = "Your message successfully inserted in process queue.";
await Task.Run(() => {
lock (_oQueue)
{
if (flagProcessing == true) //return when queue processing alredy started
{
return statusProcess; //Error ..??? how to return
}
flagProcessing = true; //else start processing the queue till there are messages.
}
});
statusProcess = ProcessMyMessage();
return statusProcess;
}
private string ProcessMyMessage() {...}
What I am missing? How to return string in between conditionally under async method that too lie inside await lock anonymous block(?). I do async in task as Do Message is flooded with lot of calls simultaneously due to exposed part of a service.
I am assuming you are asking how to access the returned value from the task.
private async Task<string> RunMessageProces()
{
var statusProcess = "Your message successfully inserted in process queue.";
var retValue = await Task.Run(() =>
{
lock (_oQueue)
{
if (flagProcessing == true) //return when queue processing alredy started
{
return "Error"; // or some such error indicator
}
flagProcessing = true; //else start processing the queue till there are messages.
}
return string.Empty; // return a string here too....
});
// if( retValue == "Error" ) { return "Error" }
statusProcess = ProcessMyMessage();
return statusProcess;
}

Categories