Talking Async
Talking Async
  • Видео 2
  • Просмотров 55 683
Talking Async Ep2: Cancellation in depth
Chris and Klemens explore and discuss Asio's cancellation support, including the new per-operation cancellation, through examples.
The example programs shown in this video are available at github.com/chriskohlhoff/talking-async.
Просмотров: 6 905

Видео

Talking Async Ep1: Why C++20 is the Awesomest Language for Network Programming
Просмотров 49 тыс.3 года назад
Game-changing new Asio features, C 20 coroutines, and live coding, with Chris & Klemens. The example programs shown in this video are available at github.com/chriskohlhoff/talking-async.

Комментарии

  • @spacechild2
    @spacechild2 17 дней назад

    The coroutine version is really neat!

  • @ahmetsaidtekkurt7832
    @ahmetsaidtekkurt7832 4 месяца назад

    more videos please, it is very hard for me to fully comprehend asio without bright minds like yours

  • @and_then_I_whispered
    @and_then_I_whispered 4 месяца назад

    We're getting an introduction from the developer of Asio itself. Nice!

  • @wolpumba4099
    @wolpumba4099 6 месяцев назад

    *Summary* *General Topic:* Exploring cancellation support in Asio, including the new per-operation cancellation. *1. [**0:52**] Basic Cancellation with a Watchdog Timer:* - [2:51] Add a timer and deadline to the proxy class. - [3:22] Implement a watchdog loop: - [3:32] Set timer expiry to deadline. - [3:38] Asynchronously wait for the timer. - [3:48] If deadline passed, stop the proxy by closing sockets; otherwise, continue looping. - [4:55] Start watchdog when read/write operations begin. - [4:47] Stop watchdog and initialize timer on shutdown. - [5:31] This approach is a "blunt tool" as it cancels everything after timeout. *2. [**10:30**] Using "cancel" and Handling Race Conditions:* - [10:41] Replaced "close" with "cancel" for finer control. - [11:12] Introduced a "stopped" flag to handle potential race conditions between operation completion and cancellation. - [12:29] Highlighted the race condition inherent in `async_write`, which composes multiple write operations. - [14:48] Demonstrated the use of a completion condition to check for cancellation between `async_write_some` calls. *3. [**19:10**] Per-Operation Cancellation with Signals and Slots:* - [19:29] Introduced the new per-operation cancellation feature. - [19:31] Added a heartbeat timer and associated cancellation signal. - [19:17] Modified server-to-client read operation: - [21:08] If canceled, send a heartbeat message instead. - [23:07] Connected the operation's cancellation slot to the heartbeat signal. - [23:14] Showcased how `bind_cancellation_slot` associates an operation's cancellation with a signal. *4. [**25:07**] Cancellation Slots: Low-Level Building Blocks:* - [25:14] Explained the signal/slot mechanism for per-operation cancellation: - [25:21] A signal is emitted to request cancellation. - [25:27] Operations are connected to signals through slots. - [25:27] Slots invoke registered handlers upon receiving a signal. - [27:20] Emphasized that cancellation slots are low-level mechanisms and shouldn't be managed directly in high-level code. *5. [**27:45**] Parallel Groups for Simplified Cancellation:* - [27:57] Replaced the separate heartbeat loop with `asio::parallel_group`. - [28:23] Demonstrated the use of a parallel group for concurrently running the read operation and the heartbeat timer. - [33:42] Highlighted `asio::make_parallel_group` for simplified cancellation management within the group. - [32:56] Illustrated how different wait options (e.g., `wait_for_all`, `wait_for_one`) influence cancellation behavior within the group. *6. [**36:19**] Customizing Cancellation in Asynchronous Operations:* - [37:05] Implemented a custom `async_wait_for_signal` operation. - [37:23] Demonstrated the use of `asio::async_initiate` for implementing custom operations. - [44:08] Illustrated how to integrate cancellation slots into custom operations: - [44:15] Retrieve the cancellation slot using `asio::get_associated_cancellation_slot`. - [44:49] Assign a cancellation handler to the slot if connected. *7. [**47:11**] Cancellation Types and Guarantees:* - [47:50] Explained different cancellation types: - [48:05] *Terminal:* Side effects may have occurred, rendering the object unusable. - [49:05] *Partial:* Some progress is made, the object's state is known, and the operation can potentially be resumed. - [50:00] *Total:* No observable side effects, allowing for operation repetition. - [55:38] Showcased adjusting cancellation behavior based on the requested cancellation type using `asio::cancellation_type`. - [55:38] Demonstrated building an operation (buffered message reading) supporting total cancellation on top of lower layers that only provide partial cancellation. *Key Takeaways:* * Asio offers various mechanisms for handling cancellation. * The `asio::cancellation_slot` is a powerful low-level building block for cancellation. * High-level constructs like co-routines and `asio::make_parallel_group` simplify cancellation management. * Cancellation guarantees (terminal, partial, total) provide information about the object's state after cancellation. i used gemini 1.5 pro to summarize the transcript

  • @wolpumba4099
    @wolpumba4099 6 месяцев назад

    *Zusammenfassung (Deutsch)* Dieses Video mit Chris Kohlhoff (Autor von Asio) und Clemens untersucht, wie sich C++20 zu einem Kraftpaket für die Netzwerkprogrammierung entwickelt hat. Es vergleicht den älteren C++11-Ansatz mit den eleganten Möglichkeiten, die durch Coroutinen und aktualisierte Asio-Funktionen eingeführt wurden. *Problem: Aufbau eines asynchronen TCP-Proxy-Servers* Ziel ist es, ein Programm zu erstellen, das als Man-in-the-Middle-TCP-Proxy fungiert. Dies beinhaltet: * Abhören eingehender Clientverbindungen. * Bei Herstellung einer Verbindung eine separate Verbindung zu einem Zielserver herstellen. * Bidirektionales Weiterleiten von Daten zwischen Client und Zielserver. * All dies muss asynchron geschehen, um Effizienz und Skalierbarkeit zu gewährleisten. *C++11-Lösung (und ihre Grenzen)* *(**0:00**)* Das ursprüngliche Beispiel mit C++11-Features verdeutlicht die Abhängigkeit von Callbacks (Lambdas) für asynchrone Operationen. Obwohl funktional, führt dieser Ansatz zu: * *Verschachtelte Strukturen:* Ineinander verschachtelte Callbacks machen den Code schwer lesbar und verständlich (Callback-Hölle). * *Einzelner `io_context`:* Ein einzelner `io_context` fördert zwar ein Single-Threaded-Modell, kann aber zum Engpass werden. * *Geteilter Zustand mit `shared_ptr`:* Die gemeinsame Nutzung von Daten zwischen Callbacks erfordert eine sorgfältige Verwaltung mit `shared_ptr`, um Dangling Pointer zu vermeiden. *Auftritt C++20: Die Coroutinen-Revolution* *(**14:02**)* C++20-Coroutinen verändern mit `co_await` das asynchrone Spiel grundlegend. Wichtige Verbesserungen, die im Video demonstriert werden: * *Synchroner Programmierstil:* Coroutinen ermöglichen es, asynchrone Operationen innerhalb von `awaitable<void>`-Funktionen so zu schreiben, als wären sie synchron, wodurch die Übersichtlichkeit des Codes erheblich verbessert wird. Dies wird durch die Verwendung von `co_await` erreicht, wodurch die Ausführung der Coroutine angehalten wird, bis die erwartete asynchrone Operation abgeschlossen ist, und danach automatisch fortgesetzt wird. * *(**15:30**) Keine rekursiven Callback-Ketten mehr:* Mit `co_await` kann asynchroner Code mit Standard-Schleifen (`for`, `while`) geschrieben werden, wodurch die für Callback-basierten Code typischen rekursiven Funktionsaufrufe überflüssig werden, was zu einem flacheren, besser lesbaren Code führt. * *(**18:37**) `co_spawn` für Nebenläufigkeit:* Das Starten unabhängiger Operationsketten gleichzeitig wird mit `co_spawn` zum Kinderspiel. Jede Kette stellt unabhängige Aufgaben dar wie z.B.: * *Behandlung einer neuen Clientverbindung:* Wenn der Proxy eine neue Verbindung akzeptiert, startet `co_spawn` eine dedizierte Kette von Operationen (Coroutine `proxy`), um diese neue Clientverbindung unabhängig zu verwalten. * *(**21:38**) Ausführen der Listener-Schleife:* Sogar der Haupt-Listener, der für die Annahme neuer Verbindungen zuständig ist, wird über `co_spawn` als separate Operationskette (Coroutine `listen`) gestartet, so dass er wirklich gleichzeitig mit anderen Operationen abläuft. * *(**24:38**) Explizites Warten vs. Spawnen:* Das Video zeigt den Unterschied zwischen `co_await` und `co_spawn`: * *`co_await`:* Dient dazu, innerhalb der aktuellen Coroutine auf den Abschluss einer asynchronen Operation zu warten, bevor es weitergeht. * *`co_spawn`:* Erzeugt eine neue Coroutine, möglicherweise in einem anderen Thread, wodurch die gespawnte Coroutine unabhängig und parallel ausgeführt werden kann, während die aktuelle Coroutine fortgesetzt wird. * *(**30:04**) Einführung von `use_nothrow_awaitable`:* Für eine elegante Fehlerbehandlung ohne Exceptions wird mit Asios `as_tuple`-Adapter ein benutzerdefinierter Vervollständigungstoken (`use_nothrow_awaitable`) erstellt. Dieser Adapter ändert das Verhalten der asynchronen Funktionen so, dass sie ein Tupel zurückgeben, das einen `error_code` und das Ergebnis enthält. Das Programm wird dann so aktualisiert, dass es den `error_code` nach jeder `co_await`-Anweisung weitergibt und überprüft, wobei strukturierte Bindungen verwendet werden, um die Arbeit mit dem Tupel zu vereinfachen. *(**35:23**) Vorbereitung auf Timeouts und verfeinerte Abbruchmöglichkeiten:* Zu diesem Zeitpunkt ist im Video eine solide Basis mit Coroutinen geschaffen, die die nächsten Schritte ermöglicht, die sich mit der Implementierung von Timeouts befassen und die Abbruchmöglichkeit einzelner Operationen für eine detailliertere Steuerung und ein effizienteres Ressourcenmanagement demonstrieren. * Adressierung von Abbrüchen und Timeouts* * *(**35:23**) Einbinden von Timeouts:* Timeouts sind von grundlegender Bedeutung für eine robuste Netzwerkkommunikation. Das Video zeigt zwei Ansätze: * *Watchdog-Coroutinen:* Dedizierte `watchdog`-Coroutinen laufen unabhängig und überwachen Fristen. Wenn eine Frist abläuft, werden entsprechende Aktionen wie Abbrüche oder das Schließen von Verbindungen durchgeführt. * *Timeouts innerhalb von Operationen:* Für eine feinere Kontrolle können Timer (`asio::steady_timer`) direkt mit Lese-/Schreibvorgängen kombiniert werden, indem `co_await` und logische Operatoren verwendet werden, um zeitlich begrenzte asynchrone Aktionen zu erstellen. * *(**40:57**) Abbruch einzelner Operationen:* Die verbesserte Unterstützung von Asio für Abbrüche ermöglicht es Ihnen, Abbruchsignale an bestimmte Operationen zu richten. Diese granulare Steuerung ist entscheidend, um Timeouts sauber zu handhaben und Datenverluste in einem mehrstufigen Proxy-Server zu vermeiden. * *(43:01) Kombination von Awaitables mit logischen Operatoren: * Ein leistungsstarker Aspekt von Coroutinen ist die Möglichkeit, logische Operatoren (`||` und `&&`) direkt mit `awaitable`-Objekten zu verwenden. Dies ermöglicht eine intuitive Steuerung des Programmablaufs: * *`||` (logisches ODER):* Führt `awaitable`-Objekte gleichzeitig aus, der Ausdruck ist abgeschlossen, sobald *eines* von ihnen abgeschlossen ist, wobei die restlichen optional abgebrochen werden. Perfekt für Timeouts, bei denen nur einer erfolgreich sein muss. * *`&&` (logisches UND):* Führt `awaitable`-Objekte gleichzeitig aus, der Ausdruck ist erst dann abgeschlossen, wenn *alle* abgeschlossen sind. Dies ermöglicht es, auf den Abschluss einer Gruppe von Aufgaben zu warten, ohne dass eine bestimmte Reihenfolge eingehalten werden muss. *(**46:08**) Eliminieren von gemeinsamem Zustand:* Coroutinen vereinfachen die Verwaltung der Lebensdauer von Natur aus. Das Video zeigt wie man: * *Referenzen übergibt:* Lokale Variablen, sogar Sockets, können dank der durch den Gültigkeitsbereich der Coroutine vorgegebenen Lebensdauer sicher als Referenzen an Coroutinen-Funktionen übergeben werden. * *Strukturierte Bindungen für Ergebnisse:* In Verbindung mit Fehlercodes machen die strukturierten Bindungen von C++17 das Entpacken des Ergebnisses von `co_await` ausdrucksstark. *Abschließende Gedanken* * C++20, zusammen mit einer ausgereiften asynchronen Netzwerkbibliothek wie Asio, ist die perfekte Kombination aus Leistung und Entwicklerproduktivität für Netzwerkanwendungen. * Coroutinen machen den Code lesbarer, verständlicher und fördern das Schreiben von wartbarerem asynchronem C++-Code. * Die im Video gezeigten Techniken verdeutlichen einen modernen und eleganten Stil der asynchronen C++-Netzwerkprogrammierung. i used gemini 1.5 pro to summarize the youtube transcript and github source code

  • @wolpumba4099
    @wolpumba4099 6 месяцев назад

    *Summary* This video, featuring Chris Kohlhoff (author of Asio) and Clemens, explores how C++20 has become a powerhouse for network programming. It contrasts the older C++11 approach with the elegant capabilities introduced by coroutines and updated Asio features. *Problem: Building an Asynchronous TCP Proxy Server* The goal is to create a program that acts as a man-in-the-middle TCP proxy. This involves: * Listening for incoming client connections. * Upon connection, establishing a separate connection to a target server. * Relaying data bi-directionally between the client and the target server. * All this needs to happen asynchronously for efficiency and scalability. *C++11 Solution (and its Limitations)* *(**0:00**)* The original example, using C++11 features, highlights the reliance on callbacks (lambdas) for asynchronous operations. While functional, this approach leads to: * *Nested Structures:* Callbacks nested within callbacks make code hard to read and reason about (callback hell). * *Single `io_context`:* While promoting a single-threaded model, a lone `io_context` can become a bottleneck. * *Shared State with `shared_ptr`:* Sharing data between callbacks necessitates careful management with `shared_ptr` to avoid dangling pointers. * Enter C++20: The Coroutine Revolution * *(**14:02**)* C++20 coroutines fundamentally change the asynchronous game with `co_await`. Key improvements demonstrated in the video: * *Synchronous-Like Flow:* Coroutines allow asynchronous operations within `awaitable<void>` functions to be written as if they were synchronous, greatly increasing code clarity. This is achieved using `co_await` to suspend the coroutine's execution until the awaited asynchronous operation completes, resuming automatically afterward. * *(**15:30**)* *No More Recursive Callback Chains:* With `co_await`, asynchronous code can be written with standard loops (`for`, `while`), eliminating the need for recursive function calls typical in callback-based code, resulting in flatter, more readable code. * *(**18:37**)* *`co_spawn` for Concurrency:* Launching independent chains of operations concurrently becomes straightforward with `co_spawn`. Each chain represents independent tasks like: * *Handling a New Client Connection:* When the proxy accepts a new connection, `co_spawn` starts a dedicated chain of operations (`proxy` coroutine) to manage this new client connection independently. * *(**21:38**)* *Running the Listener Loop:* Even the main listener, responsible for accepting new connections, is launched as a separate chain of operations (`listen` coroutine) via `co_spawn`, making it run truly concurrently with other operations. * *(**24:38**)* *Explicitly Waiting vs. Spawning:* The video demonstrates the difference between `co_await` and `co_spawn`: * *`co_await`:* Used to wait for an asynchronous operation to complete within the current coroutine before proceeding further. * *`co_spawn`:* Creates a new coroutine, potentially on a different thread, allowing independent, parallel execution of the spawned coroutine while the current coroutine continues. * *(**30:04**)* *Introducing `use_nothrow_awaitable`:* To handle errors gracefully without exceptions, a custom completion token (`use_nothrow_awaitable`) is created using Asio's `as_tuple` adapter. This adapter changes the asynchronous functions' behavior to return a tuple containing an `error_code` and the result. The program is then updated to propagate and check the `error_code` after each `co_await` statement, using structured bindings to simplify working with the tuple. *(**35:23**) Preparing for Timeouts and More Refined Cancellation:* At this point, the video establishes a solid base with coroutines, enabling the next steps that delve into timeout implementations and demonstrate per-operation cancellation for more granular control and efficient resource management. *Addressing Cancellation and Timeouts* * *(**35:23**) Incorporating Timeouts:* Timeouts are fundamental to robust networking. The video explores two approaches: * *Watchdog Coroutines:* Dedicated `watchdog` coroutines run independently and monitor deadlines. If a deadline expires, appropriate actions like cancellation or closing connections are taken. * *Timeouts within Operations:* For finer-grained control, timers (`asio::steady_timer`) can be combined directly with read/write operations using `co_await` and logical operators to create time-limited asynchronous actions. * *(**40:57**) Per-Operation Cancellation:* Asio's improved cancellation support lets you target cancellation signals at specific operations. This granular control is crucial for gracefully handling timeouts and avoiding data loss in a multi-stage proxy server. * *(43:01) Combining Awaitables with Logical Operators: * A powerful aspect of coroutines is the ability to use logical operators (`||` and `&&`) directly with `awaitable` objects. This enables intuitive control flow: * *`||` (Logical OR):* Runs `awaitable` objects concurrently, the expression completes as soon as *any* of them finish, optionally canceling the remaining ones. Perfect for timeouts where you only need one to succeed. * *`&&` (Logical AND):* Runs `awaitable` objects concurrently, the expression completes only when *all* of them have finished. This allows waiting for a group of tasks to complete without any specific order. *(**46:08**) Eliminating Shared State:* Coroutines, by their nature, make lifetime management easier. The video shows how to: * Pass References: Local variables, even sockets, can be safely passed as references into coroutine functions due to the predictable lifetime provided by the coroutine scope. * Structured Bindings for Results: When combined with error codes, C++17's structured bindings make unpacking the result of `co_await` expressive. *Final Thoughts * * C++20, alongside a mature asynchronous networking library like Asio, is a perfect blend of performance and developer productivity for networking applications. * Coroutines make code more readable, easier to reason about, and encourage writing more maintainable asynchronous C++. * Techniques shown in the video showcase a more modern and elegant style of asynchronous C++ network programming. i used gemini 1.5 pro to summarize the youtube transcript and github source code

  • @__hannibaalbarca__
    @__hannibaalbarca__ 7 месяцев назад

    This month should be only for asio library application training, i really need that.

  • @Slicc12345
    @Slicc12345 Год назад

    best video as a introduction to asio coroutines! had problems finding anything else

  • @Slicc12345
    @Slicc12345 Год назад

    best video as a introduction to asio coroutines!

  • @menkaur
    @menkaur 2 года назад

    holy shit this is awesome

  • @willvan6956
    @willvan6956 2 года назад

    Impressive!

  • @yuew.3167
    @yuew.3167 2 года назад

    i got it !!!

  • @lefteriseleftheriades7381
    @lefteriseleftheriades7381 2 года назад

    Love it. What a great library. Excellent work

  • @jiecheng2969
    @jiecheng2969 2 года назад

    The most amazing C++ talk I’ve seen recently. Thank you for making this great tutorial! Please keep on making videos on boost::asio!!

  • @rodionmontsarj5296
    @rodionmontsarj5296 2 года назад

    Thank you Chris and Klemens for great talk! At 17:30 step_2.cpp is_stopped() method is left not modified from previous example. I believe it should be: bool is_stopped() const { return stopped_; }

  • @brewtaldeafmedal1262
    @brewtaldeafmedal1262 2 года назад

    C++ isn't so great? Are you kidding me? When was the last time you saw a language with such an ability and performance with network programming? C++ puts the game in another level, and we will be blessed if we ever see a language with its performance and expressiveness again. Clojure breaks records. Rust breaks records. C++ breaks the rules. You can keep your statistics. I prefer the magic.

    • @trevortrevose9124
      @trevortrevose9124 2 года назад

      Zig supports cross compilation and is more flexible but ight

    • @sandwich5344
      @sandwich5344 Год назад

      @@trevortrevose9124 if it Aint C then it aint for me! There's a reason why everything was build up from C

    • @coderstoner3362
      @coderstoner3362 Год назад

      @@sandwich5344 Possibly because C was created before other languages like c++?

  • @ignaciosharpe4129
    @ignaciosharpe4129 3 года назад

    great job

  • @thenoblegnuwildebeest3625
    @thenoblegnuwildebeest3625 3 года назад

    When will we be able to expect a new episode? Very much enjoyed the first two.

  • @HimanshuRastogizzz
    @HimanshuRastogizzz 3 года назад

    There are few videos on ASIO, Now we have a whole channel dedicated to ASIO.

  • @SamWhitlock
    @SamWhitlock 3 года назад

    This video feels like some portal into the future. I really have to watch it carefully to learn the new ASIO ways and co_await!

  • @RishabhRD
    @RishabhRD 3 года назад

    are these awaitable types going to land in c++23... and executor TS is proposing task<> types (atleast in libunifex, it is there at progress) are both going to coexist in c++23

  • @RishabhRD
    @RishabhRD 3 года назад

    solarized theme....

  • @Sol66459
    @Sol66459 3 года назад

    Super good talk. Loved the format with the two of you and the live coding. Really enjoyed it, great work on an awesome framework.

  • @reinterpret_cast
    @reinterpret_cast 3 года назад

    Does io_context spawn any threads internally or does it call epoll/select/... entirely inside the run() method?

    • @Traian-Enache
      @Traian-Enache 2 месяца назад

      Huge necropost, and i think you have figured out already, but I’ll leave this here in case anybody stumbles across this, with the same question. It does NOT* use threads internally. On Windows, it uses IOCP, and on Linux, depending on what version the kernel has, it either uses epoll, or io_uring for newer kernels. *It does use a single separate thread for DNS resolutions, started by the first resolution, and on Windows, another single thread for all timers (implementing a timer queue); on Linux it uses timerfd_create() for each timer

    • @reinterpret_cast
      @reinterpret_cast 2 месяца назад

      @@Traian-Enache Thanks, I actually didn't know about the DNS resolutions!

  • @estanb980
    @estanb980 3 года назад

    Have golang-like channel?

  • @mjKlaim
    @mjKlaim 3 года назад

    Audio is better, thanks! Now I need to watch very focused, it's not an easy topic........

  • @Hipony-vb9nl
    @Hipony-vb9nl 3 года назад

    Fantastic talk, Klemens especially asks great questions!

  • @Chabster
    @Chabster 3 года назад

    ruclips.net/video/hHk5OXlKVFg/видео.html Elaborate on this topic please. Completion handler should only be called once, regardless of the operation is completed or canceled. Where is the race condition?

    • @talkingasync4596
      @talkingasync4596 3 года назад

      The race is a problem when you happen to call cancel *after* the operation has completed but *before* the completion handler is called. If the code in the completion handler only considers the arguments passed to the handler (for example, the error code says the operation was successful) then the cancellation is lost.

    • @Chabster
      @Chabster 3 года назад

      @@talkingasync4596 That is clear. But in this case operation can be considered complete, so you're canceling something non-cancelable. I mainly refer to existing asio workarounds that require additional code to prevent both timeout handler and completion handler to execute (when both happen to be posted to a queue roughly at the same time). Cancellation should guarantee only a single execution of the operation completion handler, right?

  • @maksimsivyy5684
    @maksimsivyy5684 3 года назад

    Thanks for amazing content! Are you going to cover some advanced multithread-wise stuff in next episodes?

  • @PaweSkalczynski
    @PaweSkalczynski 3 года назад

    It seemed so effortless when you do it Chris! Thank you for amazing piece of technology!

  • @headlibrarian1996
    @headlibrarian1996 3 года назад

    Any chance of getting rid of the co_spawns in the hot path? A coroutine pool, however that might be implemented, would eliminate that overhead, most importantly the dynamic allocation of the coroutine frame, for the same reason you want a thread pool rather than to spawn a new thread on every connection.

    • @Hipony-vb9nl
      @Hipony-vb9nl 3 года назад

      Since a Coroutine needs to allocate undeterministic amount of memory - you're better of with overriding global new/delete and make a static memory storage in that case. IIRC there were PoCs of providing an allocator to the awaitable as a function parameter, since it can customize allocations in there too.

  • @FRANKPOTTER2
    @FRANKPOTTER2 3 года назад

    asio is getting better and better

  • @dattiennguyen6712
    @dattiennguyen6712 3 года назад

    Great to see new Asio features demonstrated!

  • @DarkKnightUNS2008
    @DarkKnightUNS2008 3 года назад

    awesome talk. Looking forward to next episode

  • @PraveenKumar-hg1lf
    @PraveenKumar-hg1lf 3 года назад

    Amazing talk, although I will admit I don't understand a lot it. Anyway keep up the good work.