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".
How do I do some cleanup after checking if the cancellation token has been canceled but what I've found is if I try to do anything it the below code, everything works as expected except the cancellation, as soon as await LeaveGroup(SIGNALR_NETWORK_TEST_GROUP); is called the thread ends and nothing after it happens I've tried several different calls and it doesn't matter it just ends immediately instead of waiting for me to clean things up before I return.
What am I missing here? I even found a couple of examples from Microsoft that lead me to believe I'm on the right track but it doesn't let me execute anything else after I've checked if it's been canceled.
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation
https://learn.microsoft.com/en-us/dotnet/standard/threading/how-to-listen-for-cancellation-requests-by-polling
static void Main()
{
try
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var signalRClient = new SignalRClient();
Task.Run(async () => await signalRClient.Start(cancellationToken), cancellationToken);
Console.ReadKey();
cancellationTokenSource.Cancel();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public async Task Start(CancellationToken cancellationToken)
{
var signalRConnectionInfo = await GetConnectionInfo();
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConnectionInfo.Url, options =>
{
options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string>("OnUpdate", param =>
{
Console.WriteLine(param);
});
await hubConnection.StartAsync();
if (hubConnection.State == HubConnectionState.Connected)
await JoinGroup(SIGNALR_NETWORK_TEST_GROUP);
while (hubConnection.State == HubConnectionState.Connected)
{
if (cancellationToken.IsCancellationRequested)
{
Debug.WriteLine("Cancellation Requested!");
await LeaveGroup(SIGNALR_NETWORK_TEST_GROUP);
throw new OperationCanceledException("SignalR connection cancelled!", cancellationToken);
}
}
When I run the application for the first time, I am doing some initialization steps like asking for name and the communication mode(chat or speak), I used BeginDialogAsync to start the respective prompt dialog. But after responding to the prompt, it goes back to the MainDialog.cs I created, to check user input with LUIS, instead of going to the next waterfall step.
CODE
OptionsDialog.cs
private void InitializeWaterfallDialog()
{
var waterfallSteps = new WaterfallStep[]
{
InitialStepAsync,
FinalStepAsync
};
AddDialog(new WaterfallDialog($"{nameof(OptionsDialog)}.mainFlow", waterfallSteps));
AddDialog(new ChoicePrompt($"{nameof(OptionsDialog)}.communicationMode"));
InitialDialogId = $"{nameof(OptionsDialog)}.mainFlow";
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync($"{nameof(OptionsDialog)}.communicationMode",
new PromptOptions
{
Prompt = MessageFactory.Text("Please select how you want to continue interacting with me."),
Choices = ChoiceFactory.ToChoices(new List<string> { "Chat", "Speak" }),
}, cancellationToken);
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var attachments = new List<Attachment>();
var reply = MessageFactory.Attachment(attachments);
switch (((FoundChoice)stepContext.Result).Value)
{
case "Chat":
reply.Text = "Type something to start";
break;
case "Speak":
reply.Text = "This option is yet to be configured. Continuing with chat option. Type something to start";
break;
}
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
return await stepContext.EndDialogAsync(null, cancellationToken);
}
}
MainDialog.cs
private void InitializeWaterfallDialog()
{
var waterfallSteps = new WaterfallStep[]
{
InitialStepAsync,
FinalStepAsync
};
AddDialog(new OptionsDialog($"{nameof(MainDialog)}.options", _botStateService));
AddDialog(new GreetingDialog($"{nameof(MainDialog)}.greeting", _botStateService));
AddDialog(new RevenueDialog($"{nameof(MainDialog)}.revenue", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new QuarterlyRevenueDialog($"{nameof(MainDialog)}.quarterlyRevenue", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new ProfitDialog($"{nameof(MainDialog)}.profit", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new QuarterlyProfitDialog($"{nameof(MainDialog)}.quarterlyProfit", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new StockPriceDialog($"{nameof(MainDialog)}.stockPrice", _botStateService, _botServices, _financialServices, _currencyService));
AddDialog(new PersonNameDialog($"{nameof(MainDialog)}.personName", _botStateService, _botServices));
AddDialog(new FarewellDialog($"{nameof(MainDialog)}.farewell", _botStateService, _botServices));
AddDialog(new WaterfallDialog($"{nameof(MainDialog)}.mainFlow", waterfallSteps));
InitialDialogId = $"{nameof(MainDialog)}.mainFlow";
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
try
{
UserProfile userProfile = await _botStateService.UserProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile());
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(stepContext.Context, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult?.GetTopScoringIntent();
if(userProfile.HasRestartedConversation)
{
userProfile.HasRestartedConversation = false;
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.options", null, cancellationToken);
}
else
{
switch (topIntent?.intent)
{
case "GreetingIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.greeting", null, cancellationToken);
case "FindRevenueIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.revenue", null, cancellationToken);
case "FindQuarterlyRevenueIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.quarterlyRevenue", null, cancellationToken);
case "FindProfitIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.profit", null, cancellationToken);
case "FindQuarterlyProfitIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.quarterlyProfit", null, cancellationToken);
case "FindStockPriceIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.stockPrice", null, cancellationToken);
case "FindPersonNameIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.personName", null, cancellationToken);
case "FarewellIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.farewell", null, cancellationToken);
case "CommunicationSelectIntent":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.options", null, cancellationToken);
case null:
break;
default:
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Sorry, I don’t have an answer for that. " +
$"(Please try something like 'What is revenue of google?')"), cancellationToken);
break;
}
}
await _botStateService.UserProfileAccessor.SetAsync(stepContext.Context, userProfile);
}
catch (Exception e)
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Sorry, there was an error sending your message. Please try again."), cancellationToken);
}
return await stepContext.NextAsync(null, cancellationToken);
}
After giving a response to the prompt in InitialStepAsync in OptionsDialog, it should go the next waterfall step, FinalStepAsync but it is going to the MainDialog and checking the response with LUIS.
The activity trigger code in the main bot program, DialogBot.cs:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occured during the turn.
await _botStateService.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
await _botStateService.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
_logger.LogInformation("Running dialog with Message Activity.");
// Run the Dialog with the new message Activity.
await _dialog.Run(turnContext, _botStateService.DialogStateAccessor, cancellationToken);
}
Run method:
public static async Task Run(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor<DialogState> accessor, CancellationToken cancellationToken = default(CancellationToken))
{
var dialogSet = new DialogSet(accessor);
dialogSet.Add(dialog);
var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
if (results.Status == DialogTurnStatus.Empty)
{
await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
}
}
The issue here is that after responding to the prompt, it is going to BeginDialogAsync instead of ContinueDialogAsync which is only happening the first time I run the bot. If I trigger the OptionsDialog any time later, the waterfall steps are being run properly.
There is a function which try to scan given folder. While scanning, there is a cancel button available to user. but it doesn't cancel the scanning on first attempt, however it succeed on next attempt.
Here's my code skeleton:
private CancellationTokenSource _tokenSource;
CallingMethod()
{
var cancelationToken = GetCancelationToken();
Task.Run(() => { MethodA(true, stringVal, cancelationToken); }, cancelationToken);
}
internal void MethodA(bool isCreateNewRequested, string directoryPath, CancellationToken token)
{
bool sucess = true;
if (isCreateNewRequested)
sucess = MethodB(directoryPath, token);
if (token.IsCancellationRequested) return;
//some more code
}
private bool MethodB(string directoryPath, CancellationToken token)
{
var fileEntries = ProcessSubDirectories(directoryPath);
var totalFileCount = fileEntries.Count;
foreach (var fileEntry in fileEntries)
{
if (token.IsCancellationRequested) break;
//dosomething here
}
if (token.IsCancellationRequested) return false;
}
private CancellationToken GetCancelationToken()
{
_tokenSource?.Cancel();
_tokenSource = new CancellationTokenSource();
return _tokenSource.Token;
}
First you have to create simple cancellation token:
CancellationToken cToken = new CancellationToken();
just like that. This token has to be passed to async method, and then:
CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cToken);
Then use this cts.
if(cts.Cancelled) //written from head, so there may be other way to check it
cts.Token.ThrowIfCancellationRequested();
or:
cts.Cancel(true); //if you want to cancel manually something
cts.Token.ThrowIfCancellationRequested();
You cannot just return. You have to throw to cancel the operation.
This code is using botframework 4
I have rootdialog which is calling an another dialog called choicedialog .From the choicedialog i am returning the DialogturnResult(waiting) from BeginDialogAsync method.After that the bot wait for the input from the user.
when the user enter something it should call the ContinueAsync Method in the ChoiceDialog.But the bot is calling the Rootdailog ContinueAsync .
what is the reason for this ?How can i solve this?
in the Controller onTurnAync Method
async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
// examine results from active dialog
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await dc.BeginDialogAsync(nameof(RootDialog));
break;
case DialogTurnStatus.Waiting:
// The active dialog is waiting for a response from the user, so do nothing.
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
}
}
rootDialog
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext context, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = context.Context.Activity;
context.SendTyping(activity);
var response = DataFromService();
if (response == null || response.StatusCode != 1)
{
await context.PostAsync(Messages.StandardErrorMessage);
}
if (response.Data != null)
{
return await dialog.BeginDialogAsync(context);
}
else
{
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
ChoiceDialog
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext context, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
choiceStep.SaveEntityDataInContext(context: context);
IList<IMessageActivity> messages = GenerateMessageActivity(context);
if (messages.IsCollectionValid())
{
foreach (var message in messages)
{
await context.PostActivityToUser(message);
}
}
var dialogResult = new DialogTurnResult(DialogTurnStatus.Waiting);
return dialogResult;
}
I don't see you saving the changes to DialogState anywhere. Usually this is done at the end of OnTurnAsync. You could possibly be using AutoSaveStateMiddleware, but you didn't mention that (and, frankly, I wouldn't recommend it).
Specifically you need to call SaveChangesAsync on the BotState instance that you called CreateProperty<DialogState>. Usually this is ConversationState.
If you don't do this, every turn will start with an empty dialog stack, thus the behavior you're describing where the root dialog is always being run.