Skip to content

Commit 242fdfd

Browse files
committed
Original contribution
1 parent a3f7889 commit 242fdfd

File tree

1 file changed

+35
-18
lines changed

1 file changed

+35
-18
lines changed

tutorials/scripting/c_sharp/c_sharp_basics.rst

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -393,16 +393,20 @@ Using ``async``/``await``
393393
-------------------------
394394

395395
You might face a scenario where you must ``await`` a method call.
396-
You will notice that when you use ``await``, you are required to mark the method you use it in as ``async``, and change the return type to ``Task`` or ``Task<T>``.
397-
Consequently, you must call your now ``async`` method using ``await``, which propogates the problem all the way up the call chain.
398-
This is why many people compare ``async``/``await`` to a "zombie virus", because it tends to spread once introduced.
396+
You will notice that when you use ``await``, you are required to mark the method you use it in as ``async``,
397+
and change the return type to an awaitable type, such as ``Task`` or ``Task<T>``.
398+
Consequently, you must call your now ``async`` method using ``await``,
399+
which propagates the problem all the way up the call chain.
400+
This is why many people compare ``async``/``await`` to a "zombie virus",
401+
because it tends to spread once introduced.
399402

400403
In Godot, the conclusion to this spread is the entry point methods of a node, such as ``_Ready()`` or ``_Process()``.
401404
You will notice that the return types of these methods are ``void`` rather than ``Task``.
402405
It is considered conventional wisdom in C# to avoid ``async void`` at all times, with the exception of event handlers.
403406
The problem is that it is impossible to change the signatures of these methods since they are defined by the classes they inherit.
404407

405-
There are a couple options to address this problem, but each option comes with its own caveats and considerations. To compare these options, we will work with the following script:
408+
There are a couple options to address this problem, but each option comes with its own caveats and considerations.
409+
To compare these options, we will work with the following script:
406410

407411
.. code-block:: csharp
408412
@@ -412,8 +416,8 @@ There are a couple options to address this problem, but each option comes with i
412416
413417
public partial class AsyncTestNode : Node
414418
{
415-
int _taskCount = 0;
416-
DateTime start;
419+
private int _taskCount = 0;
420+
private DateTime start;
417421
public override void _Ready()
418422
{
419423
start = DateTime.Now;
@@ -484,7 +488,10 @@ The second option is to mark the entry point method as async anyway.
484488
await DoStuffAsync(.5, nameof(_Process));
485489
}
486490
487-
Both the manual task starting method and the ``async void`` method behave identically to an equivalent script written in GDScript that uses its version of the ``await`` keyword; the method pauses once it reaches the ``await``ed method call.
491+
Both the manual task starting method and the ``async void`` method
492+
behave identically to an equivalent script written in GDScript
493+
that uses its version of the ``await`` keyword;
494+
the method pauses once it reaches the ``await``-ed method call.
488495
The game loop will run until the task completes, at which point execution will continue on the main thread.
489496

490497
Let's look at the output from the above code:
@@ -498,10 +505,14 @@ Let's look at the output from the above code:
498505
00:00.53 Thread: 1 Task 2 completed
499506
00:00.53 Thread: 1 Task 3 completed
500507
501-
The first observation from the output is that the game loop continues without waiting for the completion of the ``_Ready()`` method.
502-
This continuation can introduce issues, especially if methods like ``_Process()`` rely on variables or objects that get initialized only after the ``await`` call in ``_Ready()``.
503-
Such asynchronous timing problems are termed `Race Condition <https://en.wikipedia.org/wiki/Race_condition#In_software/>`_, which is one of the main hazards when working with asynchronous code.
504-
To avoid errors due to race conditions, be sure to check that values are initialized before you use them, and use ``IsInstanceValid()`` after ``await``ing a function.
508+
The first observation from the output is that the game loop continues
509+
without waiting for the completion of the ``_Ready()`` method.
510+
This continuation can introduce issues, especially if methods like ``_Process()``
511+
rely on variables or objects that get initialized only after the ``await`` call in ``_Ready()``.
512+
Such asynchronous timing problems are termed `Race Condition <https://en.wikipedia.org/wiki/Race_condition#In_software/>`_,
513+
which is one of the main hazards when working with asynchronous code.
514+
To avoid errors due to race conditions, be sure to check that values are initialized before you use them,
515+
and use ``IsInstanceValid()`` after you ``await`` a function.
505516

506517
Here is a pattern you can adopt to avoid race conditions:
507518

@@ -513,8 +524,8 @@ Here is a pattern you can adopt to avoid race conditions:
513524
[Export] public int EntityID { get; set; } = 1;
514525
515526
readonly SomeCustomRepository _db = new();
516-
int _health;
517-
bool _init;
527+
private int _health;
528+
private bool _init;
518529
519530
// We will check IsInvalid after we await async methods
520531
// Otherwise we risk the continuation running in a disposed context
@@ -569,7 +580,8 @@ Here is a pattern you can adopt to avoid race conditions:
569580
}
570581
571582
The third option is to execute the ``async`` method synchronously.
572-
This is most commonly done when you need to use an asynchronous method from a third party library that has no synchronous equivalent,
583+
This is most commonly done when you need to use an asynchronous
584+
method from a third party library that has no synchronous equivalent,
573585
and it is not feasible to convert everything upstream to ``async``.
574586

575587
.. code-block:: csharp
@@ -598,7 +610,12 @@ Let's look at the output from the above code:
598610
00:01.03 Thread: 4 Task 3 started from _Process
599611
00:01.53 Thread: 4 Task 3 completed
600612
601-
The output from running the tasks synchronously shows that the tasks executed in the expected order for synchronous operations.
602-
The output also shows that the code was executed on Thread 4, rather than Thread 1 like in the first two options.
603-
This is important to keep in mind, because any code that is not executed on the main thread (Thread 1) cannot interact with the scene tree, as it is not thread safe.
604-
You should use ``CallDeferred``/``SetDeferred``, ``CallThreadSafe``/``SetThreadSafe``, or ``CallDeferredThreadGroup``/``SetDeferredThreadGroup`` to interact with thread safe objects or APIs from threads other than the main thread.
613+
The output from running the tasks synchronously shows that
614+
the tasks executed in the expected order for synchronous operations.
615+
The output also shows that the code was executed on Thread 4,
616+
rather than Thread 1 like in the first two options.
617+
This is important to keep in mind, because any code that is not
618+
executed on the main thread (Thread 1) cannot interact with the scene tree, as it is not thread safe.
619+
You should use ``CallDeferred``/``SetDeferred``, ``CallThreadSafe``/``SetThreadSafe``,
620+
or ``CallDeferredThreadGroup``/``SetDeferredThreadGroup`` to interact with thread
621+
safe objects or APIs from threads other than the main thread.

0 commit comments

Comments
 (0)