How to set timeout for WebSocket.ReceiveAsync - websocket

It seems WebSocket.ReceiveAsync waits potentially forever for data. Is there a way to set an inactivity timeout? Below is conceptually how the code would look:
public async Task DoListening(AspNetWebSocketContext context)
{
WebSocketReceiveResult receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
if (inactivity timeout ocurred)
{
DoInactivityAction();
}
else
{
DoReceiveAction();
}
}

You can use a custom cancellation token with timeout.
public async Task DoListening(AspNetWebSocketContext context)
{
var timeOut = new CancellationTokenSource(500).Token;
WebSocketReceiveResult receiveResult;
try
{
receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), timeOut );
DoReceiveAction();
}
catch(OperationCancelledException)
{
DoInactivityAction();
}
}
Untested code.

Related

Next step after PromptAsync is not called

I have this simple dialog, with 2 simple waterfall steps.
The user sees "How may I help you today?" and when it answers, nothing happens. I can't get Validate to work.
Am I missing something? I'm using SDK 4.1.5.
public ComplaintsDialog() : base(nameof(ComplaintsDialog))
{
var steps = new WaterfallStep[]
{
Ask,
Validate
};
AddDialog(new WaterfallDialog("flow", steps));
AddDialog(new TextPrompt("asking"));
}
private static async Task<DialogTurnResult> Ask(WaterfallStepContext sc, CancellationToken cancellationToken)
{
return await sc.PromptAsync("asking", new PromptOptions { Prompt = new Activity { Text = "How may I help you today?", Type= ActivityTypes.Message} }, cancellationToken);
}
private static async Task<DialogTurnResult> Validate(WaterfallStepContext sc, CancellationToken cancellationToken)
{
var answer = sc.Result;
await sc.Context.SendActivityAsync(answer.ToString());
return await sc.EndDialogAsync();
}
}
UPDATE
I tried to simplify the code, and this is how I currently call ComplaintsDialog directly from the main bot.
It looks like the stack is always empty when it gets to await dc.ContinueDialogAsync();, so it's going into a loop and start ComplaintsDialog over and over again
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
// Create dialog context.
var dc = await _dialogs.CreateContextAsync(turnContext);
switch (turnContext.Activity.Type)
{
case ActivityTypes.Message:
// Continue outstanding dialogs.
await dc.ContinueDialogAsync();
// Begin main dialog if no outstanding dialogs/ no one responded.
if (!dc.Context.Responded)
{
await dc.BeginDialogAsync(nameof(ComplaintsDialog));
}
break;
case ActivityTypes.ConversationUpdate:
if (dc.Context.Activity.MembersAdded != null && dc.Context.Activity.MembersAdded.Any())
{
foreach (var newMember in dc.Context.Activity.MembersAdded)
{
if (newMember.Id != dc.Context.Activity.Recipient.Id)
{
await dc.BeginDialogAsync(nameof(WelcomeDialog));
}
}
}
break;
}
}
The code example you provided looks like it should be working so the problem is probably elsewhere.
My guess is that you are starting the ComplaintsDialog inside of a WaterfallStep (from another dialog) so make sure that you are calling the BeginDialogAsync method like this:
return await stepContext.BeginDialogAsync(nameof(ComplaintsDialog));
instead of:
await stepContext.BeginDialogAsync(nameof(ComplaintsDialog));
If this is not the error probably more information is necessary
Update
Your problem is on the OnTurnAsync method. You're not saving the new turn into the conversation state. The Message case on your switch should look like this:
case ActivityTypes.Message:
if (dc.ActiveDialog == null)
{
await dc.BeginDialogAsync(nameof(ComplaintsDialog), cancellationToken);
}
else
{
await dc.ContinueDialogAsync(cancellationToken);
}
await _accessors.ConversationState.SaveChangesAsync(turnContext);
break;
And your constructor:
private readonly MyBotAccessors _accessors;
public MyBot(MyBotAccessors accessors, ILoggerFactory loggerFactory)
{
...
_accessors = accessors ?? throw new System.ArgumentNullException(nameof(accessors));
...
}
SaveChangesAsync documentation

A very long message for LUIS dialog resets dialog state

Using bot framework emulator v.3.5.36, if a user sends long text (about 1K characters), emulator silently resets dialog stack back to root dialog, without any errors or warnings. (see the screenshot below.)
Is there a declared message limit for bot framework?
Is there a way for bot to handle such situations and warn user instead of this silent something?
There is nothing really specific about the code at all:
[LuisModel("{GUID}", "{CODE}", LuisApiVersion.V2, domain: "westeurope.api.cognitive.microsoft.com", threshold: 0.5)]
[Serializable]
public class LuisSearchDialog2 : LuisDialog<object>
{
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
await context.PostAsync(JsonConvert.SerializeObject(result));
context.Wait(this.MessageReceived);
}
}
A simple approach would be to check the length of your message in the MessageController and decide whether you want to process it or not.
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if (activity.Text != null && activity.Text.Length > 200)
{
var errorReply = activity.CreateReply();
errorReply.Text = "Well well, that is too much of data. How about keeping it simple? How can I help you?";
await connector.Conversations.ReplyToActivityAsync(errorReply);
}
else
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
}
}
The reason is that base LuisDialog doens't handle failed API requests (in case if query is too long, it returns 414 code). So the simplest way to handle such errors is to override MessageReceived as follows:
[Serializable]
public class LuisSearchDialog2 : LuisDialog<object>
{
protected override async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> activity)
{
try
{
await base.MessageReceived(context, activity);
}
catch(HttpRequestException e)
{
// Handle error here
//await context.PostAsync("Error: " + e.ToString());
context.Wait(this.MessageReceived);
}
}
}

ResumeAfter method is already called without calling context.done in the next dialog

I have implemented a structure where a QnA dialog is first started. If the QnA Dialog cannot solve the problem then it starts a Luis Dialog which has some main functionalities defined. Based on those main functionalities I start specific dialogs that can solve the problem.
My problem is that when I try to start LuisDialog from QnAMaker, it starts another LuisDialog to for conversation, That dialog doesn't stop on with wait method and automatically calls ResumeAfter method immediately after executing.
QnADialog:
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults results)
{
if (results == null || results.Answers.Count==0 || !IsConfidentAnswer(results) || results.Answers.FirstOrDefault().Score<0.75) {
await context.Forward(new MainLuisDialog(), MessageReceived, context.Activity.AsMessageActivity(), CancellationToken.None);
}
}
First Luis Dialog:
[LuisIntent(ErrorFileLink)]
public async Task ErrorFileLinkIntentHandler(IDialogContext context, LuisResult result) {
await context.Forward(new ErrorFileLinkDialog(), CallBackHandler, context.Activity.AsMessageActivity(), CancellationToken.None);
}
private async Task CallBackHandler(IDialogContext context, IAwaitable<object> result)
{
try {
var returnedResult = await result;
if (returnedResult as string == "done")
context.Done(false);
}
catch (Exception e) {
}
}
2nd Luis Dialog:
[LuisIntent(MainAppIntent)]
public async Task MainAppIntentHandler(IDialogContext context, LuisResult result)
{
if(context.GetPrivateConversationData<SyncIssueStates>(CurrentDialogState) == SyncIssueStates.ExpectingSyncCompleteMessage)
{
await context.PostAsync(Utility.GetResourceString("SYNC_ISSUE_PLEASE_WAIT_SYNC_COMPELTE"));
context.Wait(MessageReceived);
return;
}
await context.PostAsync(Utility.GetResourceString("SYNC_ISSUE_GET_ERROR_MESSAGE"));
context.SetPrivateConversationData(CurrentDialogState, SyncIssueStates.ExpectingErrorMessage);
context.Wait(MessageReceived);
}
CallBackHandler Method in First Luis Dialog is called right after Forward is executed.
I think this behavior is due to the fact that the QnAMakerDialog calls context.Done(true); inside the DefaultWaitNextMessageAsync method. ref: https://github.com/Microsoft/BotBuilder-CognitiveServices/blob/master/CSharp/Library/QnAMaker/QnAMaker/QnAMakerDialog.cs#L203
Try overriding the DefaultWaitNextMessageAsync method instead:
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults results)
{
if (results == null || results.Answers.Count == 0 || !IsConfidentAnswer(results) || results.Answers.FirstOrDefault().Score < 0.75)
{
await context.Forward(new FirstDialog(), AfterForward, context.Activity.AsMessageActivity(), CancellationToken.None);
context.Wait(base.MessageReceivedAsync);
}
else
{
await base.DefaultWaitNextMessageAsync(context, message, results);
}
}

Delayed dialog with resume handler

I have the following scenario I think I'm doing it wrong.
I have a RootDialog which calls a ResultDialog. The ResultDialog presents the user a list of results (using HeroCard).
The ResultDialog closes itself using context.Done(..) after the message was posted.
In the RootDialog- AfterResultDialog Resume handler I want to ask the user if he has found the matching result, using another dialog (NotificationDialog), but I want to do that after 30 seconds.
After some research, this seems like it must be done using proactive messages.
It this example, I found a way to post the NotificationDialog in a proactive way.
private async Task AfterResultDialog(IDialogContext context, IAwaitable<object> result)
{
var message = await result as IMessageActivity;
var conversationReference = context.Activity.ToConversationReference();
ConversationStarter.conversationReference = JsonConvert.SerializeObject(conversationReference);
t = new Timer(timerEvent);
t.Change(30000, Timeout.Infinite);
context.Wait<string>(NotificationDialogAfter);
}
public void timerEvent(object target)
{
t.Dispose();
ConversationStarter.Resume();
}
But the problem I have is that I'm interested in the result of this NotifcationDialog to know what the user wants to do next.
But all examples I found using proactive-messages do not regard the result of a proactive message with dialog:
public class ConversationStarter
{
public static string conversationReference;
public static async Task Resume()
{
var message = JsonConvert.DeserializeObject<ConversationReference>(conversationReference).GetPostToBotMessage();
var client = new ConnectorClient(new Uri(message.ServiceUrl));
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
var task = scope.Resolve<IDialogTask>();
// here it seems only to be possible to call a dialog using Void
var dialog = new NotificationDialog();
task.Call(dialog.Void<object, IMessageActivity>(), null);
await task.PollAsync(CancellationToken.None);
await botData.FlushAsync(CancellationToken.None);
}
}
}
The NotificationDialogAfter handler should decide based on the user input which dialog to call next:
private async Task NotificationDialogAfter(IDialogContext context, IAwaitable<string> result)
{
var whereToContinue = await result;
if (whereToContinue.Equals("Start over"))
{
context.ClearAllConversationDataKeys();
context.Call(new TagDialog(), this.TagDialogAfter);
}
else if (whereToContinue == "Tell friends")
{
context.Call(new TellFriendsDialog(), TellFriendsDialogAfter);
}
else if (whereToContinue == "Feedback")
{
context.Call(new FeedbackDialog(), this.FeedbackDialogAfter);
}
}
So what I basically want is that the result of the NotificationDialog is forwarded to the NotificationDialogAfter handler which the Root dialog is waiting for.
Is this even possible?
I solve the problem by defining static continue handlers (in GlobalContinueHandler), that I can provide inside the NotificationDialog, when calling other dialogs.
[Serializable]
public class NotificationDialog : IDialog<string>
{
public Task StartAsync(IDialogContext context)
{
PromptDialog.Choice(context, Resume, new List<string> { "yes", "no" },
"Found what you're looking for?");
return Task.CompletedTask;
}
private async Task Resume(IDialogContext context, IAwaitable<string> result)
{
var message = await result;
if (message == "yes")
{
context.Call(new SignupDialog(), GlobalContinueHandler.SignupDialogAfter);
}
else
{
context.Call(new FeedbackDialog(), GlobalContinueHandler.FeedbackDialogAfter);
}
}
}
I'm really not fan of this solution but for now it seems to work.

OData Connection in Xamarin Form

My code crashes and gives the following error on simulator. It attempts to run the try block in the GetDataFromOdataService() method and throws an error and also issue an alert. I am using Xamarin.Form
using Simple.OData.Client;
using System.Threading.Tasks;
private ODataClient mODataClient;
protected async override void OnAppearing ()
{
base.OnAppearing ();
await InitializeDataService ();
await GetDataFromOdataService();
}
public async Task <bool> InitializeDataService(){
try {
mODataClient = new ODataClient ("http://services.odata.org/Northwind/Northwind.svc/");
}
catch {
await DisplayAlert("Error", "Connection Error", "OK", "Cancel");
System.Diagnostics.Debug.WriteLine("ERROR!");
}
return true;
}
public async Task<bool> GetDataFromOdataService (){
try {
myCustomers= await mODataClient.For("Customers").Top(10).FindEntriesAsync();
}
catch {
await DisplayAlert("Error", "Connection Error", "OK", "Cancel");
System.Diagnostics.Debug.WriteLine("ERROR!");
}
return true;
}
Couple issues:-
In the constructor it was doing var list = new ListView() which constrained it locally than setting the class level scope variable. This was therefore adjusted to list = new ListView().
The other thing, was in the GetTheData function where the items source was being assigned as list.ItemsSource = myList; where it needed changing to list.ItemsSource = Customers;.
I've repackaged the zip file up and sent to you. Let me know if this works for you? You should now be able to see all your customers in the ListView.

Resources