From 1821ea15bbda167434d5c1c58fb958bf2a6359d3 Mon Sep 17 00:00:00 2001 From: Hsu Still <341464@gmail.com> Date: Tue, 20 Mar 2018 15:02:38 +0800 Subject: [PATCH] Add examples & tidy commands section + Modified the headings to fit the formal aspect of docs. + Added more details regarding RunMode --- docs/faq/Commands.md | 70 ++++++++++++++++++-------- docs/faq/samples/commands/runmode-cmdattrib.cs | 7 +++ docs/faq/samples/commands/runmode-cmdconfig.cs | 9 ++++ 3 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 docs/faq/samples/commands/runmode-cmdattrib.cs create mode 100644 docs/faq/samples/commands/runmode-cmdconfig.cs diff --git a/docs/faq/Commands.md b/docs/faq/Commands.md index 948b7639d..27e00d43a 100644 --- a/docs/faq/Commands.md +++ b/docs/faq/Commands.md @@ -18,7 +18,7 @@ custom preconditions. [RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute [Preconditions Addons]: https://github.com/Joe4evr/Discord.Addons/tree/master/src/Discord.Addons.Preconditions -## I'm getting an error about `Assembly#GetEntryAssembly`. What now? +## I'm getting an error about `Assembly#GetEntryAssembly`. You may be confusing [CommandService#AddModulesAsync] with [CommandService#AddModuleAsync]. The former is used to add modules @@ -64,37 +64,64 @@ A brief example of service and dependency injection can be seen below. By default, all commands are executed on the same thread as the gateway task, which is responsible for keeping the connection from -your client to Discord alive. When you execute a long-running task, +your client to Discord alive. By default, when you execute a command, this blocks the gateway from communicating for as long as the command task is being executed. The library will warn you about any long running event handler (in this case, the command handler) that -persists for more than 3 seconds. +persists for **more than 3 seconds**. To resolve this, the library has designed a flag called [RunMode]. -There are 2 main `RunMode`s. One being `RunMode.Sync`, which is the -default; another being `RunMode.Async`. `RunMode.Async` essentially -calls an unawaited Task and continues with the execution without -waiting for the command task to finish. You should use -`RunMode.Async` in either the [CommandAttribute] or the -[DefaultRunMode] flag in `CommandServiceConfig`. -Further details regarding `RunMode.Async` can be found below. + +There are 2 main `RunMode`s. + 1. `RunMode.Sync` (default) + 2. `RunMode.Async` + +You can set the `RunMode` either by specifying it individually via +the `CommandAttribute`, or by setting the global default with +the [DefaultRunMode] flag under `CommandServiceConfig`. + +# [CommandAttribute](#tab/cmdattrib) + +[!code-csharp[Command Attribute](samples/commands/runmode-cmdattrib.cs)] + +# [CommandServiceConfig](#tab/cmdconfig) + +[!code-csharp[Command Service Config](samples/commands/runmode-cmdconfig.cs)] + +*** + +*** + +> [!IMPORTANT] +> While specifying `RunMode.Async` allows the command to be spun off +> to a different thread instead of the gateway thread, +> keep in mind that there will be **potential consequences** +> by doing so. Before applying this flag, please +> consider whether it is necessary to do so. +> +> Further details regarding `RunMode.Async` can be found below. [RunMode]: xref:Discord.Commands.RunMode [CommandAttribute]: xref:Discord.Commands.CommandAttribute [DefaultRunMode]: xref:Discord.Commands.CommandServiceConfig#Discord_Commands_CommandServiceConfig_DefaultRunMode -## Okay, that's great and all, but how does `RunMode.Async` work, and if it's so great, why is the lib *not* using it by default? +## How does `RunMode.Async` work, and why is Discord.NET *not* using it by default? + +`RunMode.Async` works by spawning a new `Task` with an unawaited +[Task.Run], essentially making `ExecuteAsyncInternalAsync`, the task +that is used to invoke the command task, to be finished on a +different thread. This means that [ExecuteAsync] will be forced to +return a successful [ExecuteResult] regardless of the execution. -As with any async operation, `RunMode.Async` also comes at a cost. -The following are the caveats with RunMode.Async, -1) You introduce race condition. -2) Unnecessary overhead caused by [async state machine]. -3) [ExecuteAsync] will immediately return [ExecuteResult] instead of -other result types (this is particularly important for those who wish -to utilize [RuntimeResult] in 2.0). -4) Exceptions are swallowed. +The following are the known caveats with `RunMode.Async`, + 1. You introduce race condition. + 2. Unnecessary overhead caused by [async state machine]. + 3. [ExecuteAsync] will immediately return [ExecuteResult] instead of + other result types (this is particularly important for those who wish + to utilize [RuntimeResult] in 2.0). + 4. Exceptions are swallowed. -However, there are ways to remedy #3 and #4. +However, there are ways to remedy some of these. For #3, in Discord.NET 2.0, the library introduces a new event called [CommandExecuted], which is raised whenever the command is @@ -104,7 +131,8 @@ the `RunMode` type and will return the appropriate execution result. For #4, exceptions are caught in [CommandService#Log] event under [LogMessage.Exception] as [CommandException]. -[async state machine]: https://www.red-gate.com/simple-talk/dotnet/net-tools/c-async-what-is-it-and-how-does-it-work/)) +[Task.Run]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run +[async state machine]: https://www.red-gate.com/simple-talk/dotnet/net-tools/c-async-what-is-it-and-how-does-it-work/ [ExecuteAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_ExecuteAsync_Discord_Commands_ICommandContext_System_Int32_System_IServiceProvider_Discord_Commands_MultiMatchHandling_ [ExecuteResult]: xref:Discord.Commands.ExecuteResult [RuntimeResult]: xref:Discord.Commands.RuntimeResult diff --git a/docs/faq/samples/commands/runmode-cmdattrib.cs b/docs/faq/samples/commands/runmode-cmdattrib.cs new file mode 100644 index 000000000..253acc4a9 --- /dev/null +++ b/docs/faq/samples/commands/runmode-cmdattrib.cs @@ -0,0 +1,7 @@ +[Command("process", RunMode = RunMode.Async)] +public async Task ProcessAsync(string input) +{ + // Does heavy calculation here. + await Task.Delay(TimeSpan.FromMinute(1)); + await ReplyAsync(input); +} \ No newline at end of file diff --git a/docs/faq/samples/commands/runmode-cmdconfig.cs b/docs/faq/samples/commands/runmode-cmdconfig.cs new file mode 100644 index 000000000..22b356aa3 --- /dev/null +++ b/docs/faq/samples/commands/runmode-cmdconfig.cs @@ -0,0 +1,9 @@ +public class Setup +{ + private readonly CommandService _command; + public Setup() + { + var config = new CommandServiceConfig{DefaultRunMode = RunMode.Async}; + _command = new CommandService(config); + } +} \ No newline at end of file