I've come across the problem regarding asynchronous tasks in a dictionary in a couple programs now, and I can't wrap my head around how to solve it.
I have an asynchronous task that looks like this:
MessageEventArgs.Channel.SendMessage("somestring");
MessageEventArgs
is a class from a 3rd party library that I declare statically at the start of my program:
public static MessageEventArgs meargs;
The program listens for events in an IRC channel and does actions based on text commands. Rather than have a giant switch statement for each command, I wanted to make a dictionary that matched the string to the method. Not all are simply sending messages, so I can't just store the string to send. It looks like this:
public static Dictionary<string, Task> myDict= new Dictionary<string, Task>()
{
{"!example", MessageEventArgs.Channel.SendMessage("HelloWorld") }
}
In Main(), I call:
MessageReceived += async (s,e) =>
{
meargs = e;
await myDict[e.Message.Text];
}
What I've come to realize is that when the dictionary is instantiated, it tries to call SendMessage, as it is an async task. MessageEventArgs isn't instantiated until after, so it throws an exception. Is there a way to go about storing a reference to these functions in a dictionary? I found solutions using delegates, but they didn't seem to work with void
methods, and async
methods (to my knowledge) can only return void
or Task
.
Thanks in advance!
C# has a language-level asynchronous programming model, which allows for easily writing asynchronous code without having to juggle callbacks or conform to a library that supports asynchrony. It follows what is known as the Task-based Asynchronous Pattern (TAP).
Async/Await on a Separate Thread If a predefined method returns a Task , you simply mark the calling method as async and put the await keyword in front of the method call. It's helpful to understand the control flow associated with the await keyword, but that's basically it.
To return Boolean from Task Synchronously, we can use Task. FromResult<TResult>(TResult) Method. This method creates a Task result that's completed successfully with the specified result. The calling method uses an await operator to suspend the caller's completion till called async method has finished successfully.
You use the void return type in asynchronous event handlers, which require a void return type. For methods other than event handlers that don't return a value, you should return a Task instead, because an async method that returns void can't be awaited.
Instead of creating the tasks immediately, store factories:
public static Dictionary<string, Func<Task>> myDict= new Dictionary<string, Func<Task>>()
{
{"!example", () => MessageEventArgs.Channel.SendMessage("HelloWorld") }
}
await myDict[e.Message.Text]();
If I were writing this, I would create an IrcCommand
class with a virtual ExecuteCommand
function. Then create a subclass for every distinct command I wanted to be able to run. Each subclass would override the ExecuteCommand
function with the logic needed to invoke the command. ExecuteCommand
could take an argument of type Channel
corresponding the the channel to invoke the command.
At startup I would create a Dictionary<string, IrcCommand>
that was populated with a Key
of the command text, and a Value
of a new instance of the subclass implementing that command.
When a command comes in I would pull the IrcCommand
instance from the dictionary with the matching key and call the ExecuteCommand
function on it, passing in the Channel
the command was received from.
It could happen something like this. I don't actually have visual studio open to verify my syntax, so I may have a few errors in here.
An implementation of some commands:
public class ExampleCommand : IrcCommand
{
public override void ExecuteCommand(Channel channel)
{
channel.SendMessage("Hello World");
}
}
public class DisconnectCommand : IrcCommand
{
public override void ExecuteCommand(Channel channel)
{
channel.Disconnect();
}
}
Registering commands at application start:
private void RegisterCommands()
{
_commands.add("!example", new ExampleCommand());
_commands.add("!disconnect", new DisconnectCommand());
}
Executing the command when received:
_commands(commandText).ExecuteCommand(e.channel);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With