Application (Slash) Command says "The application did not respond" when replying with Components - discord.net

Following the guide I found on the Discord.Net documentation site, I've built a command with the following definition:
Imports Discord
Imports Discord.Interactions
Public Class TestCommands
Inherits InteractionModuleBase(Of SocketInteractionContext)
Public Property Commands As InteractionService
Private _handler As CommandHandler
Public Sub New(ByVal handler As CommandHandler)
_handler = handler
End Sub
<SlashCommand("faq", "View frequently asked questions")>
Public Async Function FAQ() As Task
Dim FAQMenu As New SelectMenuBuilder
Dim FAQSelect As New ComponentBuilder
With FAQMenu
.WithPlaceholder("What's your question?")
.WithCustomId("faq_menu")
.WithMinValues(1)
.WithMaxValues(1)
.AddOption("Question #1?", "faq1")
.AddOption("Question #2?", "faq2")
.AddOption("Question #3?", "faq3")
.AddOption("Question #4?", "faq4")
End With
FAQSelect.WithSelectMenu(FAQMenu)
Await ReplyAsync("Select one of the following Frequently Asked Questions:", components:=FAQSelect.Build)
End Function
End Class
When I run my bot and type /faq in my Discord client, it correctly displays my selection menu, but right above it I see a message saying "The application did not respond"
I'm guessing that the bot is waiting for the selection, and I understand that there's a short timeout for the bot to receive input, but I definitely don't want my users to see that and think that there's a problem with the bot. Is there a better way to post the select menu component to prevent this message from popping up?
EDIT: Sorry, I forgot to include the CommandHandler object definition. Perhaps that's where the timeout is occurring. There's a lot of unfinished code in here, but maybe it'll help diagnose why I'm getting this message:
Imports System.Reflection
Imports Microsoft.Extensions.Configuration
Imports Discord
Imports Discord.Interactions
Imports Discord.WebSocket
Imports Ichthus.Bot
Public Class CommandHandler
Private ServerLogChannel As UInt64 = 0
Public Async Function InitializeAsync() As Task
' add the public modules that inherit InteractionModuleBase<T> to the InteractionService
Await Settings.BotCommands.AddModulesAsync(Assembly.GetEntryAssembly(), Settings.BotProvider)
' process the InteractionCreated payloads to execute Interactions commands
AddHandler Settings.Bot.InteractionCreated, AddressOf HandleInteraction
AddHandler Settings.Bot.ButtonExecuted, AddressOf HandleButton
AddHandler Settings.Bot.SelectMenuExecuted, AddressOf HandleMenu
' process the command execution results
AddHandler Settings.BotCommands.SlashCommandExecuted, AddressOf SlashCommandExecuted
AddHandler Settings.BotCommands.ContextCommandExecuted, AddressOf ContextCommandExecuted
AddHandler Settings.BotCommands.ComponentCommandExecuted, AddressOf ComponentCommandExecuted
End Function
Private Function ComponentCommandExecuted(ByVal Command As ComponentCommandInfo, ByVal Context As IInteractionContext, ByVal Result As IResult) As Task
If Not Result.IsSuccess Then
Select Case Result.[Error]
Case InteractionCommandError.UnmetPrecondition
Case InteractionCommandError.UnknownCommand
Case InteractionCommandError.BadArgs
Case InteractionCommandError.Exception
Case InteractionCommandError.Unsuccessful
Case Else
End Select
End If
Return Task.CompletedTask
End Function
Private Function ContextCommandExecuted(ByVal Command As ContextCommandInfo, ByVal Context As IInteractionContext, ByVal Result As IResult) As Task
If Not Result.IsSuccess Then
Select Case Result.[Error]
Case InteractionCommandError.UnmetPrecondition
Case InteractionCommandError.UnknownCommand
Case InteractionCommandError.BadArgs
Case InteractionCommandError.Exception
Case InteractionCommandError.Unsuccessful
Case Else
End Select
End If
Return Task.CompletedTask
End Function
Private Function SlashCommandExecuted(ByVal Command As SlashCommandInfo, ByVal Context As IInteractionContext, ByVal Result As IResult) As Task
If Not Result.IsSuccess Then
Select Case Result.[Error]
Case InteractionCommandError.UnmetPrecondition
Case InteractionCommandError.UnknownCommand
Case InteractionCommandError.BadArgs
Case InteractionCommandError.Exception
Case InteractionCommandError.Unsuccessful
Case Else
End Select
End If
Return Task.CompletedTask
End Function
Private Async Function HandleInteraction(ByVal Interaction As SocketInteraction) As Task
Try
' create an execution context that matches the generic type parameter of your InteractionModuleBase<T> modules
Dim Context As New SocketInteractionContext(Settings.Bot, Interaction)
Dim Result As IResult = Await Settings.BotCommands.ExecuteCommandAsync(Context, Settings.BotProvider)
If Not Result.IsSuccess Then
Select Case Result.[Error]
Case InteractionCommandError.UnmetPrecondition
Case Else
End Select
End If
Catch ex As Exception
Console.WriteLine(ex)
' if a Slash Command execution fails it is most likely that the original interaction acknowledgment will persist. It is a good idea to delete the original
' response, or at least let the user know that something went wrong during the command execution.
If Interaction.Type = InteractionType.ApplicationCommand Then
Interaction.GetOriginalResponseAsync().ContinueWith(Function(msg) msg.Result.DeleteAsync()).Wait()
End If
End Try
End Function
Private Async Function HandleMenu(ByVal Selection As SocketMessageComponent) As Task
For Each Selected As String In Selection.Data.Values
Select Case Selected
Case "faq1"
Await Selection.RespondAsync("Response #1.")
Case "faq2"
Await Selection.RespondAsync("Response #2.")
Case "faq3"
Await Selection.RespondAsync("Response #3.")
Case "faq4"
Await Selection.RespondAsync("Response #4.")
Case Else
Await Selection.RespondAsync($"{Selection.User.Mention} has selected something I don't recognize")
End Select
Next Selected
End Function
Private Async Function HandleButton(ByVal Button As SocketMessageComponent) As Task
Select Case Button.Data.CustomId
Case "faq1"
Await Button.RespondAsync($"{Button.User.Mention} has clicked the first button")
Case "faq2"
Await Button.RespondAsync($"{Button.User.Mention} has clicked the second button")
Case "faq3"
Await Button.RespondAsync($"{Button.User.Mention} has clicked the third button")
Case Else
Await Button.RespondAsync($"{Button.User.Mention} has clicked something I don't recognize")
End Select
End Function
End Class

This issue occurs because you are not responding to the interaction. ReplyAsync simply sends a message to the channel it does not send a message in response to the interaction. As such, as far as Discord is concerned, your application did not respond, just as the error says. When using Slash Commands you should use RespondAsync to send a message in response to the command. ReplyAsync is meant for standard Text Commands.
Note that Slash Command responses usually look similar to how the error message is formatted in your image. The original command is shown at the top and the command response is shown immediately below it as if you used Discord's reply feature.

Related

UWP adding UI control to list in non-uit thread

So as i'm learning more and more stuff of UWP and XAML i bumped into two issues, one is (i think) "navigation" related and the second a threading issue. What i'm trying to achieve is simple. I have two pages, one "home" and one "Settings". On the Home page i show the connected clients as Custom_Buttons. On the Settings page i can change some settings regarding the app and Connected Clients
Navigation Issue
On my MainPage is setup all my declarations and object classes i need. When i navigate to a page i pass me (that is the MainPage) through to the page i'm loading so i can use the properties and objects in the that i declared on the MainPage. Then when i load the page i use the page event OnNavigatedTo to handle the passed MainPage and do local stuf with it. When i switch often between the pages the app crashes and opens the page app.g.i.vb and point to the following code:
#If Debug AndAlso Not DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION Then
AddHandler Me.UnhandledException,
Sub(sender As Global.System.Object, unhandledExceptionArgs As Global.Windows.UI.Xaml.UnhandledExceptionEventArgs)
If Global.System.Diagnostics.Debugger.IsAttached Then
**Here--->>>** Global.System.Diagnostics.Debugger.Break()
End If
End Sub
#End If
And the navigation code:
Private Sub ListBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
If Home.IsSelected AndAlso Not ScenarioFrame.CurrentSourcePageType Is GetType(Home) Then
BackButton.Visibility = Visibility.Collapsed
ScenarioFrame.Navigate(GetType(Home), Me)
ElseIf Settings.IsSelected AndAlso Not ScenarioFrame.CurrentSourcePageType Is GetType(Settings) Then
BackButton.Visibility = Visibility.Visible
ScenarioFrame.Navigate(GetType(Settings), Me)
End If
End Sub
Threading Issue
On the MainPage I declare a class i wrote called TCP_Server. This class has a StreamSocketListener that uses the event ConnectionReceived to accept new incoming clients. I then simply create a new Object that represents a UI form of the client and pass it the StreamSocket that comes in the Event Args in the sub new. In this way each object can handles it's own Read and Write directly from the StreamSocket Then i add this new object to a ObservableCollection(Of Object) which is held in the TCP_Server Class. This list is bound to the ItemsSource of a Canvas that i use on the HomePage which is not my MainPage.
Protected Overrides Sub OnNavigatedTo(e As NavigationEventArgs)
MyBase.OnNavigatedTo(e)
If ButtonsList.ItemsSource = Nothing Then ButtonsList.ItemsSource = DirectCast(e.Parameter, MainPage).TCP_Server.Clients
End Sub
When i create this new object in the ConnectionReceived i get an error System.Exception: 'The application has called an interface that has been marshalled for another thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD)) '. It only works when i use the Dispatcher.RunAsync
Private Async Sub TCP_Listener_ConnectionReceived(sender As StreamSocketListener, args As StreamSocketListenerConnectionReceivedEventArgs) Handles TCP_Listener.ConnectionReceived
'Check if the client already excists or not.
Dim client As Client_Button = Clients.FirstOrDefault(Function(x) x.IPaddr = args.Socket.Information.RemoteAddress.ToString)
rootPage.NotifyUser("New Client connected! : [" & args.Socket.Information.RemoteAddress.ToString & "] Total Connected clients = " & Clients.Count, NotifyType.Message)
If client Is Nothing Then
Await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, Function()
'Create New object
Dim x As New Client_Button(args.Socket)
'Create new task that runs Async to process incomming data
Dim tsk As Task = Task.Run(Sub() x.ProcessClientAsync())
'Add to the task list so we can stop it later on
ClientTasks.Add(tsk)
'Add it to the Clients List so we can work with the objects
Clients.Add(x)
Return True
End Function)
Else
Await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, Function()
client = Nothing
Clients.Remove(client)
'Create New object
Dim x As New Client_Button(args.Socket)
'Create new task that runs Async to process incomming data
Dim tsk As Task = Task.Run(Sub() x.ProcessClientAsync())
'Add to the task list so we can stop it later on
ClientTasks.Add(tsk)
'Add it to the Clients List so we can work with the objects
Clients.Add(x)
Return True
End Function)
End If
End Sub
For "Navigation Issue" you described here, navigation between pages several times it will crash, please try to set NavigationCacheMode of the page to Required or Enabled as follows:
Public Sub New()
Me.InitializeComponent()
Me.NavigationCacheMode = NavigationCacheMode.Required
End Sub
Details please reference remarks of Page class. If you still have issues please provide the details about the "UnhandledException" .
For "Threading Issue", using Core​Dispatcher is the correct way and this is by design. ConnectionReceived triggered in a non-UI thread, but you invoked UI thread inside this event handle, so you need Dispatcher.RunAsync. More details you can reference this similar thread.

Dispatcher, Tasks and cancellation issues

I am writing a library to automate internet explorer. The library sets up its own message loop by starting a thread and using Dispatcher.Run. The reason for this is to keep internet explorer and mshtml on the same thread.
I then use the following in the library.
Private ExDispatcher As Dispatcher
Private Success As Boolean
Private EXThreadWaiter As Threading.AutoResetEvent
Public Async Function ClickAndWaitForNewPageAsync() As Task(Of Boolean)
Return Await Task.Run(Of Boolean)(Function()
ExDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, Sub() startClickAndWaitForNewPage())
EXThreadWaiter.WaitOne()
Debug.Print("Completed with: " + Success.ToString)
Return Success
End Function)
End Function
Private Sub startClickAndWaitForNewPage()
'do a lot of stuff and wait for internet explorer
'if we are happy and all good set Success = True
'now let the thread in the task go
EXThreadWaiter.Set()
End Sub
ClickAndWaitForNewPageAsync starts a task which is on a new thread, it immediately then uses my message loop to call startClickAndWaitForNewPage and all the Internet Explorer stuff is done. When everything is ok the variable Success is set to a value and EXThreadWaiter.Set() is called which releases the task thread and the result is returned.
A client of this library therefore uses it as shown below.
Private Async Sub someFunction()
Dim res As Boolean = Await ClickAndWaitForNewPageAsync()
If res Then
'continue on with code
Else
'do nothing
End If
End Sub
The issue I have with this is how to shutdown or stop my library which needs to happen for various reasons. I currently have this method in the same class as ClickAndWaitForNewPageAsync
Public Sub StopIt()
Success = False
EXThreadWaiter.Set()
End Sub
This method sets Success to False and lets the task continue. This mostly works fine but it is possible that the following happens.
startClickAndWaitForNewPage sets Success to True
EXThreadWaiter.Set() is called
Debug.Print("Completed with: " + Success.ToString) indicates Success is True
StopIt() runs setting Success = False
someFunction in the client code receives Success = True
This means that in someFunction it continues with its code thinking everything is ok but in fact it is not.
How can I prevent this from happening?
By the way the Cancellation Token etc i do not think will help, but I'm happy to hear differently.
What I previously used
I previously did not use Tasks. I would simply use the Dispatcher begininvoke which would mean everything is done on the right thread and the client would have to subscribe to an event that would fire when everything was done successfully. This felt untidy to me and thus I am trying this new method.

Ensure maintainability & testability in file-processing application

Problem
I want to refactor an old application which has grown over time and is awful to maintain/test. Basically the app needs to read an file an process it lines.
The file to be read can be compared to a CSV-file, where the first field determines the type of the line, something like this:
1,abcd,...
1,3423,...
2,"abc",...
5,test,...
Currently the application works like this (pseudo-code):
For Each line in file.getLines()
if (line.StartsWith("1,") then
' Process line of type 1
elseif (line.StartsWith("2,") then
' Process line of type 2
...
End If
Next
This obviously is a nightmare to maintain and test. Therefore I'd like refactor this whole thing and thought of doing it like this:
Create a parser for each line-type
Let each parser register itself to a handler, which will raise an event for each line
Code Idea
Handler:
Public Class ParserHandler
Public Event Parse(RepLine As ReportLine)
Public Event FileFinished()
Public Sub RaiseParse(RepLine As ReportLine)
RaiseEvent Parse(RepLine)
End Sub
Public Sub RaiseFileFinished()
RaiseEvent FileFinished()
End Sub
End Class
Parser:
Public Class FirstParser
Public Sub New(Handler As ParserHandler)
AddHandler Handler.Parse, AddressOf Parse
AddHandler Handler.FileFinished, AddressOf FileFinished
End Sub
Public Sub Parse(RepLine As ReportLine)
If Not RepLine.Type = 1 Then
Return
End If
' Process
End Sub
Private Sub FileFinished()
' Final stuff, eg. insert into DB
End Sub
End Class
Main:
Dim ParserHandler As New ParserHandler()
Dim FirstParser As New FirstParser(ParserHandler)
Dim SecondParser As New SecondParser(ParserHandler)
...
For Each line in file.getLines()
' Extract Id and construct ReportLine-object here
ParserHandler.RaiseParse(ReportLine)
Next
I think this looks way better than the original version, but I'm still not convinced that this is really the way to go. In case of testing the parsers with unit tests, I still have to create a handler first. This might be ok, but feels wrong as the parser should be testable separately (is it wrong?).
I like the use of events in this case, as not every parser needs to implement every possible event of the handler, which prevents me from just having an IParser interface.
What do you think of this approach, is it good to go? Are there any best practices/design pattern I should look into for a clean solution to this problem?

Update a label from a task

I'm trying to implement tasks in my program. I launch a task that will produce a log file, and after, I want to update the label to say "Log sucessfully saved".
Here is my code
Private Function Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Try
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Catch ex As Exception
End Try
Return 1
End Function
Private Function UpdateLabel(ByVal text As String)
Label1.Text = text
Return 1
End Function
I launch the task from the Main form in the Load() :
Dim tasktest = Task(Of Integer).Factory.StartNew(Function() Createlog(theList))
(I don't know if it is better to use the factory or declare as a task and then task.Start())
I have the error on the label update :
Cross-thread operation not valid: Control 'Label1' accessed from a thread
other than the thread it was created on.
Could you please explain why it doesn't work with the invoke method ? And do you have an alternative solution ?
Thanks for your help
First, UpdateLabel should be a Sub, not a Function. Second, this line is wrong:
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Read it again. You are, in order, executing the UpdateLabel function, then passing the result of that function to Me.Invoke (if you used Sub instead of Function, the compiler should have warned you about the error).
This doesn't raise any compiler errors because a Function declared without As [Type] is defaulted to As Object, that can be cast to anything. It should be:
Me.Invoke(Sub()
UpdateLabel("Log sucessfully saved")
End Sub)
To simplify, your code can be rewritten like this:
Private Sub Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Me.Invoke(Sub()
Label1.Text = "Log sucessfully saved"
End Sub)
End Sub

How do I stop/cancel a task processing a function or find a better way of doing this?

I have ...
Private Sub TestTask()
Debug.Write("Running")
For i As Integer = 0 To 60
Debug.Write(".")
System.Threading.Thread.Sleep(1000)
Next
Debug.WriteLine("Finished")
End Sub
....
Dim cts As New CancellationTokenSource
Dim oToken As CancellationToken = cts.Token
'Create HelperTask to wait for cancellation request
Dim oHelperTask As Task = Task.Factory.StartNew(Function()
'Create Task to invoke function
Dim oTask As Task = Task.Factory.StartNew(Function()
Return outerFunction.Invoke
End Function, oToken)
' wait for cancellation token if Task is not complete
While oTask.Status = TaskStatus.Running
Thread.Sleep(200)
If oToken.IsCancellationRequested Then
oToken.ThrowIfCancellationRequested()
Return Nothing
End If
End While
Return oTask.Result
End Function, oToken)
cts.cancel()
But in my debug window on visual sudio my TestTask() continues to run with ..... please anyone enlighten me. Thanks
The whole point of the CancellationToken is that the actual worker lambda (or function) should check it to see if it should stop. In your case, TestTask must have access to the token and check it after each iteration. Neither the multiple helper tasks or the checks for the task status or the cancellation request check are necessary.
The MSDN article on Task Cancelation shows how the only thing required is for the lambda to check the token, nothing more.
In your case, TestTask can respond to a cancellation with code as simple as this:
Sub Main()
Dim cts As New CancellationTokenSource
Dim token = cts.Token
Task.Factory.StartNew(Sub() TestTask(token), token)
Thread.Sleep(3000)
cts.Cancel()
Console.ReadKey()
End Sub
Private Sub TestTask(token As CancellationToken)
Console.Write("Running")
For i As Integer = 0 To 60
token.ThrowIfCancellationRequested()
Console.Write(".")
Thread.Sleep(1000)
Next
Console.WriteLine("Finished")
End Sub
The only thing needed is to pass the token to TestTask and start it like this:
Task.Factory.StartNew(Sub() TestTask(token), token)
You don't want/need 2 tasks - it's cooperative cancellation, so every task you want to end when cancel is called will need to include ThrowIfCancellationRequested (or however it should handle cancellation). There's intentionally no Thread.Abort type behavior/semantics, it's all cooperative.