You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: tutorials/scripting/c_sharp/c_sharp_basics.rst
+35-18Lines changed: 35 additions & 18 deletions
Original file line number
Diff line number
Diff line change
@@ -393,16 +393,20 @@ Using ``async``/``await``
393
393
-------------------------
394
394
395
395
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.
399
402
400
403
In Godot, the conclusion to this spread is the entry point methods of a node, such as ``_Ready()`` or ``_Process()``.
401
404
You will notice that the return types of these methods are ``void`` rather than ``Task``.
402
405
It is considered conventional wisdom in C# to avoid ``async void`` at all times, with the exception of event handlers.
403
406
The problem is that it is impossible to change the signatures of these methods since they are defined by the classes they inherit.
404
407
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:
406
410
407
411
.. code-block:: csharp
408
412
@@ -412,8 +416,8 @@ There are a couple options to address this problem, but each option comes with i
412
416
413
417
publicpartialclassAsyncTestNode : Node
414
418
{
415
-
int_taskCount=0;
416
-
DateTimestart;
419
+
privateint_taskCount=0;
420
+
privateDateTimestart;
417
421
publicoverridevoid_Ready()
418
422
{
419
423
start=DateTime.Now;
@@ -484,7 +488,10 @@ The second option is to mark the entry point method as async anyway.
484
488
awaitDoStuffAsync(.5, nameof(_Process));
485
489
}
486
490
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.
488
495
The game loop will run until the task completes, at which point execution will continue on the main thread.
489
496
490
497
Let's look at the output from the above code:
@@ -498,10 +505,14 @@ Let's look at the output from the above code:
498
505
00:00.53 Thread: 1 Task 2 completed
499
506
00:00.53 Thread: 1 Task 3 completed
500
507
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.
505
516
506
517
Here is a pattern you can adopt to avoid race conditions:
507
518
@@ -513,8 +524,8 @@ Here is a pattern you can adopt to avoid race conditions:
513
524
[Export] publicintEntityID { get; set; } =1;
514
525
515
526
readonlySomeCustomRepository_db=new();
516
-
int _health;
517
-
bool _init;
527
+
privateint_health;
528
+
privatebool_init;
518
529
519
530
// We will check IsInvalid after we await async methods
520
531
// 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:
569
580
}
570
581
571
582
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,
573
585
and it is not feasible to convert everything upstream to ``async``.
574
586
575
587
.. code-block:: csharp
@@ -598,7 +610,12 @@ Let's look at the output from the above code:
598
610
00:01.03 Thread: 4 Task 3 started from _Process
599
611
00:01.53 Thread: 4 Task 3 completed
600
612
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