 Okay, it's time. So, now I'd like to introduce our next guest, Dan Vatil. So, let me give him a big round of applause. Dan's been a maintainer in KD for ages working on Akinadi, Permanent, all sorts of places throughout the stack. And now he's going to talk to us about C++ code routines and Qt. So, hello. Thank you for the introduction, David. So, my name is Daniel. I've been working on the C++ and KD for almost over a decade now. And, well, lately I found interest in being into interesting new features in the C++ language. So, that's why I'm here today talking about coroutines and about Qt. Word of warning before I begin. Coroutines are a bit of a complex topic to explain. So, there's a lot of things to talk about and not enough time. And also, there is a lot of code in this presentation. So, you have been warned. I'm not kidding. The first slide already, if I can switch. Yes, I can switch. The first slide already is a piece of code because I think that's the best way how to explain what coroutines actually give us and what they are useful for. So, let's get into it. What do we have here? So, we have a simple regular function as you know. It's a member function called getTitle that returns a Q string. What does the function do? Well, it creates a D-Bus interface. So, let's talk to some remote service somewhere else. Well, also in your computer, but it's also D-Bus. Anyway, so, we have the interface. Now, what we do is we call player.call. So, we call some remote function called title in the remote service. Then, we just convert the response to Q D-Bus reply because that's how we can extract the response, which is type of type Q string. And then, well, then there is this thing, right? So, one thing is we have Q D-Bus reply on one side and then we have Q string on the other. How does the conversion happen? Well, Q D-Bus reply does this magically. But the thing is now, I just said, well, we just did a remote call to a remote service. So, we can't possibly have the Q string already available, right? It's a D-Bus, right? That thing does lots of things. So, if you do a D-Bus call here in the player.call, what happens is your applications needs to create a message, send it to the D-Bus D-Bus. The D-Bus D-Bus must forward it to the actual service that you want to talk to. The service must process the request, calculate the response, send the response back to the D-Bus. The D-Bus must forward the response back to you. You need to process it. And then finally, you might have the Q string that you want it, right? This all takes time. In most cases, you will just get away with doing this blocking call because what this equal sign does, well, in this case, the operation behind that is when extracting the string from the D-Bus reply, it will have to wait blocking, it will have to block the main thread and wait for the response to arrive so that we can extract the Q string and store it in the arrival, right? So, in most cases, this will work just fine, right? It will take just a split of a second. The users will not notice that you just did a blocking call in your main thread. The application will still seem responsive. Except when you are presenting your awesome application in front of a huge crowd of 200 people and your boss is looking at you and then suddenly you are standing in front of a screen and your application is frozen for 20 seconds because the remote service has crashed or is not responding for some reason. And now your application is doing a blocking call, so it's blocked. It's waiting for the D-Bus timeout to kick in and return an error instead of title. So, you see, doing blocking calls in the main thread is not a good idea. So, how can we fix this? Well, one option is we could do player.callwithcallback which would actually allow us to pass a callback that Q2 would eventually call when it receives the response. This is a problem, though. The callback, in order to be able, then we would need to change the signature of the getTitle function, right? It would no longer return Q string. It would be void. And instead, we would have to take some probably another callback from the caller that we would then call with the actual Q string that we got from the remote service. But unlikely, the caller might need to adjust their signature as well to take a callback from their caller so that they can propagate the response, right? So, using callwithcallback or Q D-Bus pending callwatcher which is a really long name for a really simple class that also does just waiting for a D-Bus reply, in both those cases, you would need to fundamentally change your API, change your design, and it would propagate to more layers of your application, probably. So, that's not really cool, right? Just because of this simple D-Bus call, we don't want to have to range in here or enter API. So, what are your options? Well, one thing that you could do is you could create a nested Q event loop inside this function and wait for the reply to arrive, then you stop the event loop and you return. So, the signature of the function doesn't change. The function still looks like it's not blocking. It actually doesn't block your UI thread because the event loop will be running nested but then using nested event loops, that's not a good idea really, right? Don't run nested event loops, especially if you're writing libraries. So, what are the options? Well, let's see if coroutines can help. So, this is the same function, except it's a coroutine now. Let's see what has changed. So, there are three things that have changed from the previous version. First is the return type. The return type now is Q Coral Task, Q String. Let's not bother ourselves with what the Q Coral is or what the task is. Just think that it's a return type that represents some sort of a asynchronous or pending operation, right? Then we have the call wait keyword, which is the main thing here. The call wait keyword, what it does, it, and then I'll get to that later. And we have call return as well. We have call wait and call return, call wait will wait for the reply to arrive. I'll dig into that later. And call return, that's basically just like return except for coroutines. The reason we cannot use regular return is because title is Q String but the return type of our function is something else. So, that wouldn't match. So, in fact, what the call return does is it sort of calls something like task.set return value or something like that and passes it the title. Anyway, look at the call wait keyword. So, in this case, what we do is we do the regular D-Bus call like we did before. We convert it to Q D-Bus reply and then we pass the Q D-Bus reply to the call wait keyword. Now, what does the call wait keyword do? The call wait keyword will trigger a bunch of code that's hidden behind that partially generated by compiler, partially provided by the coroutine library developer. In this case, me. So, what the call wait keyword, it will look at the Q D-Bus reply and it will say, huh, the Q D-Bus reply is not finished yet. I don't have the result yet. So, what it will do is it will suspend the coroutine. Suspending the coroutine means that it will take snapshot of all the local states. So, all the local variables and an instruction pointer and some other stuff. It will take the snapshot and save it somewhere in the memory. And then it will return to the caller of this function. So, even though the function has not finished yet, it hasn't reached the end, we are already returning to the caller and saving the intermediate state somewhere in the memory. So, now let's look at what the caller might look like. You might have a caller that looks like this. It's a simple function called player widget update and the player widget update, what it does, it calls the remote player, gets title, and which should get the title using the coroutine form. It's a coroutine, right? And we know that it's a coroutine. So, it might return early, right? We know that it might return early because the D-Bus response is not ready. But then we want the response here. We want the title. So, we need to await again for that coroutine to finish. So, we have like two nested coroutines. So, what happens when we call remote player get title? It will get suspended, it will return the task, the key corout task in some unfinished state, and the co-await keyword here, this co-await keyword, will look at the task and will say, well, the task is not finished. So, I will just suspend this coroutine and return to the caller. And then the caller might be co-awaiting this coroutine and so on and so on. They will be chaining up until it reaches a point where either someone blocks and waits until it is finished, or they don't care that it's a coroutine and it will just continue doing something else. So, and this is important, this fact that it always returns to the caller means that we can have this executed from the event loop. And whenever the coroutine is suspended itself, it means that the execution will return back into the event loop. So, as long, so while the coroutine is being suspended, waiting for a network reply or D-Bus reply, we can have our event loop perfectly looping. Let's look at this diagram. This is state-of-the-art presentation painting. Event loop, let's say it's cute event loop. It can be an event loop, but let's assume it's cute event loop. And this blue bar indicates that the event loop is running, right? It's doing its stuff, it's event looping. And now at some point, for some reason, it decides to call the player widget update function. It could be a timer or any other reason. Simply it calls player widget update. So player widget update starts running, right? And the event loop is blocked, it's not running. That's why it's lighter, right here. We can't have both things running at the same time. One function called another function, so the execution is now here. And if you remember, the function update called the getTitle function. So the execution moves forward further down here. And then if you remember the function getTitle, it called D-Bus call. So that sends some D-Bus message out and almost immediately returned back. So we are back in getTitle. Now what's next in getTitle was the co-wait keyword, right? So we are now in the co-wait keyword that the red box down there. And the co-wait keyword looks at the D-Bus response and says, well, the response is not ready yet. So I will suspend getTitle and I will return execution to my caller, which was player widget update. So now the execution is back in player widget update. If you remember, player widget update was co-waiting on the getTitle coroutine as well. So here we have the co-wait again. The co-wait looks at the getTitle and it says, well, the getTitle task is not finished yet. So I will suspend myself as well and return to my caller. So now the player widget update is suspended as well and the execution is back in the event loop. And now the event loop is happily running, processing your events, your UI is reacting to user input and so on. While these two functions are suspended, right? They are saved somewhere in the memory. They are not actually being executed. It's the event loop that's running. And now suddenly, let's not go into details how that happens, but let's assume that when the D-Bus reply arrives finally to your application, it somehow gets triggered from the event loop, goes into some magical D-Bus handler and the D-Bus handler sets the response as finished. And remember, there is a coroutine that is bound to that response. So what happens? The coroutine is awakened. So the coroutine is awakened. We are now in the getTitle coroutine which was waiting for the D-Bus response. The coroutine does something, whatever was after the co-wait keyword. So that was assigning into queueString and then co-returning that queueString back to its caller. So the next step, it returns the title to its caller which was playerRigetUpdate. PlayerRigetUpdate might do some other stuff with that title, you know, set it as a title on some label or whatnot and then it reaches its end and it finishes. So the execution now, this is weird. It doesn't necessarily return to the event loop because playerRigetUpdate was called from the event loop. It returns back to getTitle and then from getTitle, it returns back to the D-Bus handler because the D-Bus handler is the one who actually resumed the coroutine. Right, so the execution after resuming follows from the one who resumed them. Finally, the D-Bus handler is finished and it returns back to the event loop and the event loop continues event looping happily. So this is somewhat what happens when you use coroutines, right? When you find a place where you sort of want to wait for approach for some operation asynchronously, but you don't want to rewrite your entire code base to be asynchronous, you can just use code wait. And it's not that simple, but we'll get to that later. Here is the same graph only if the D-Bus call was blocking. Right, if we did the right, basically the thing that we had in the original snippet which was waiting in a blocking manner for the response to arrive. If you compare that, when we are waiting in a blocking manner, the event loop is not running at all the whole time. While with coroutines, the event loop is executing most of the time. Those blocks are actually not proportional to the actual execution time. So in reality, it would be mostly dark blue and a very little, very small, you know, the times when the event loop is not running or is blocked would be much smaller than this even. So here we can see that by using coroutines without actually changing the code, right? We just throw in a few keywords and magically our code does not block the event anymore. So now let's look at coroutines a bit more in general. You may have heard about coroutines before if you use some other languages. Coroutines are common in Python, JavaScript, Kotlin, Go, Rust, they seem all to have coroutines. It almost sounds like C++ is last to the party, but it's not. Anyway, coroutines is a function. It can be suspended at any point and then resumed again from that point where it was suspended. When the coroutine is suspended, the execution returns to its color, which we just shown. And when the coroutine is resumed, it's resumed as if it was called from the function that has resumed it. We've shown that as well with the weird debuts handler sort of calling the function, right, suspended function. What's the most important for me about coroutines is that coroutines allow you to write asynchronous code in synchronous manner. That means no, you don't need call back hell. You don't need, you know, 15 different sling those slots and then lots of connections between them. It allows you to write a code as if it was synchronous and then you just throw a code weight keyword here and there and magically your code doesn't block the event loop and it's asynchronous. What's the warning though? It does not mean that it will improve your performance. It will not make your application faster. It might help make your application more responsive, but coroutines will not make your application faster. The reason is that not necessarily, of course, but in most cases, the overhead of suspending and resuming coroutines is small, but there is some, right? So, you know, if you co-wait for a network reply here and there and then, you know, you co-wait some debuts requests here and there, it's not measurable. The impact is not measurable, but, you know, if you would do this in a tight loop, then yeah, that there would be a measurable slowdown because of the overhead of suspending and resuming the coroutines. So, coroutines in C++ and the C++ coroutines were introduced in C++ 20. So in the last version, it's perfectly fine supported by GCC and MSVC in latest versions. Clank is a bit problematic. So it's in a way supported for a long time since Clank 5, but Clank still supports only the technical specification. They still consider their implementation of coroutines to be experimental. So that makes it kind of difficult because if you write your code to be portable to both our compilers, then for GCC, everything is in the namespace stud, in the stud namespace, while then in Clank, everything is in the stud experimental namespace. So you need to do some magic with macros usually to get it working. The biggest disadvantage is that because it's experimental in Clank, it only works with the LLVM lib C++ standard library. You can't use Clank and the standard GNU lib STD C++ and coroutines that doesn't fly. And this becomes problematic when you try to use coroutines with some distribution provided packages because distributions, Linux distributions usually use the GCC standard library, like the lib STD C++. While if you try to use Clank and you build your application with coroutines and lib C++, there might be a simple problems because lib C++ and still lib C++ are not compatible on the ABI level. What's interesting is that in the C++ 20, they, the committee, they actually introduce mostly just the language extension. They introduce the keywords and describe the machinery that's supposed to be hidden behind those keywords. And the, but what they did is they only specify the bare minimum tools in the standard library that are needed by compiler developers to be able to implement coroutines and compilers. And for library writers, you want to write coroutine libraries, right? It's not like there are no ready-made, you know, thing like tasks, for instance, that you see the core of the task return type. There is no such thing in the standard library that you could just start using immediately as a programmer. The reason is that the idea was that the committee introduced all the tooling that's necessary to write coroutine libraries. And then they led the wider community to actually come up with some implementation so that we can see how coroutines are used in the real world in production, how, what is the best approach. And then hopefully the next version of C++ or version after, there will be some proposals to standardize one or two different approaches to coroutine so that after then, regular developers can just come, include the coroutines header and start using coroutines out of the box with the standard library. For now, that's not possible. You need to write lots of the machinery or you need to write the glue that can be used with the user code and the machinery for the machinery of coroutines in the language. So what has been introduced into C++ really? There are three new keywords, co-weight, co-return, and co-yield. The co-underscore prefix is there to avoid conflict with some other libraries that even before there were official coroutines in C++, there were various implementations using threads and light threads. In, for instance, in boost or other libraries and they have created macros called evade and yield, for instance. So that this was, this would clash. So they decided instead to use the co-underscore prefix to make sure that this doesn't clash with some existing implementations of coroutines in C++. The keywords, what they do, co-weight, we already saw, co-weight, suspends the coroutine and returns execution to the caller. Co-return allows us to return a value from a coroutine. It's basically like return, just for coroutines. And then the interesting thing that I haven't dug very deep into yet, but that's co-yield, which basically allows us to return repeatedly values from a function without the function actually ending, right? Normally when you return a value from a function, the function ends, but with co-yield, you can actually return the value repeatedly whenever someone asks for it without ending the function. This is useful for writing generators. In the standard library, we have a few things. This is not exhaustive, but there are like one or two more things that I didn't mention here. First is the stdcoroutine handle, which is a class that is a handle for coroutine, really, as the name says. It allows resuming the coroutine and internally, it holds pointers to where the stack is saved and where the instruction pointer and some other stuff. And then there are suspend, never suspend, always helpers which allow the coroutine to indicate whether it should or should not suspend and when it should not suspend. This is customizable, so coroutine, for instance, can decide when it's called, it can decide to immediately suspend itself and then only start executing the user code at the moment that someone starts co-weighting that coroutine, right? Other coroutines can decide to start immediately executing the user code and then they run, run, run. And, well, when someone co-weighs them, they might already get the result. So, and the suspend, never suspend, always allows to control this behavior. So how do you make a coroutine in C++? Well, first, you must use one of the keywords co-weight or co-healed. If you read the C++ standard, just adding one of those two keywords into the code magically makes it coroutine by definition, though it will not compile yet. The coroutine must have a special return type. The special return type or the return type is special because it must have a type def called promise underscore type, which, well, is a type def to some particular class, to some class which must implement some sort of a promise interface. This has nothing to do with stood promise or stood future, right? It's just the naming is similar, the idea is the same. So there must be some promise type. Usually the return type looks something like, you know, this task T, where T is the actual type of the value you want to return, so Q string or integer. And the way I think about is the task is like the interface for the color. That's what the color of the coroutine sees, while the promise type is what is used internally within the coroutine. The problem is that that's what I mentioned initially is that there is no such return type already defined in the standard library, right? So you need to write it yourself first. And in order to be able to write this return type, you need to understand coroutines a bit more in depth before you can just start using them, which is why it's not so easy to get into coroutines immediately. And of course, you must use coroutines or returns when you have a coroutine. So now, one thing that you might be wondering is this is back to one of slide five or six. When we had this code and I said, well, we have the QD bus reply and then we pass it to Co-wait. And Co-wait looks at the D-Bus reply and it says, well, the D-Bus reply is not finished. I will suspend the coroutine. And I said, what does C++ magically know about Qt? Does C++ understand QD bus reply? No, it's not like that. It's not that easy. There has to be some point, some integration point, right? Someone must have told how to figure out that QD bus reply is finished or not. And that someone is the coroutine library developer. In this case, the library that I use is, oh, well, I will get to that. But the way it works is basically the Co-wait keyword requires an argument, which is of a particular type. And the type must either already implement an interface which is called available or it's like a concept runner. So having a few functions that the Co-wait keyword will use to figure out, okay, is it finished yet or not? Or should I suspend? And how should I suspend? Or if you remember promise type from the return, from the previous slide, from the return type of the coroutine, if there is a function called evade transform, overloaded on that promise type for that particular type T. So for instance, in our case for the QD bus reply, then the Co-wait will call this away transport function and it expects that in return, it will get some sort of an awaitable again, right? So this basically, since the promise type is bound to the return type, it's bound to the return type of the coroutine. So task in this case. This means that if you pick the right return type, so you can provide the await transform overload for any type you want to support. So in this case, I have a little library that provides QCRO task as a class that implements the available interface. And internally it has the promise type defined to some custom class. And this class has a await transform overload specified for all different QS types, including QD bus reply. So that's how Co-wait learns about QD bus reply, how it is able to determine that whether QD bus reply is finished or not. The library is surprise, surprise, I call it QCRO. And it's a small library, which provides custom awaitable return type for coroutines, which that's the QCRO task. This QCRO task has the await transform implementation for various QS types where it makes sense to have some, which have some asynchronous implement, some asynchronous operation that makes sense to wait for. And it also provides some syn wrappers that provide various asynchronous operations for different types as well. I'll explain that in an example. So here for some types, I call these types like explicitly available. Those are types that they really have only one thing that makes sense to be awaited, which for instance, we have QFuture here. QFuture really has only one thing that you may want to asynchronously wait for, and that's the QFuture finishing, right? So in this case, what we have here is a Qth concurrent run. Qth concurrent run is basically what it does. It starts this lambda in a thread. Immediately returns to QFuture, and then eventually when the thread finishes, the QFuture will be finished, and it will provide the results to the caller. When you use QCoro, so if the coroutine returns QCoro task, you can just put co-wait in there, and magically the co-wait will know how to deal with QFuture, and it will be able to wait asynchronously again for the QFuture to finish. And while it's waiting, the coroutine suspended and you are possibly running in an event loop. So you can have this nice asynchronous code, sorry, synchronous-like looking code written in asynchronous way. You don't need to use QFuture watcher and this kind of stuff. Same goes with Qnetwork reply. Really Qnetwork reply has just one thing that you usually want to wait for, and that's the reply being finished. Although Qnetwork reply is also a QIO device, which on its own has some interesting things, some interesting asynchronous operations. I'll show that later. So here we create a network access manager, we call get, and because again QCoro task has an overload for Qnetwork reply pointer, you can just shove co-wait in here and magically, this will suspend your coroutine and asynchronously resume when the reply is finished and it will return the reply again. Of course you don't have to always just put co-wait immediately before the operation. What you could do here, you could take the reply pointer and then you could do some more stuff like query your database and then do some computation and then finally when you reach the point where you really already need the response from the server, that's where you start co-waiting. So you can also use it the way that you fire off an asynchronous request, then you do some processing and then when you reach a point where you need the data from the response, then you start co-waiting this. So it also allows for some level of parallelism, which is nice. Here is more complex example with Q-process. Q-process has multiple things that make sense to be co-awaited. It has multiple asynchronous operations. For this case, Q-Coro has a simple wrapper function called Q-Coro where you just put a positive Q-process and it returns a thin wrapper class called Q-Coro process. And this class implements part of the interface of Q-process, but it implements it in a way that you can again use co-wait with it. So in this case, you just start some process and then you asynchronous the wait for the process to start, then you write something in it and then again you asynchronous the wait for the process to finish. So you have a beautiful block of code in a single place that's easier to read, easier to navigate. You don't have to do any mental exercises about jumping through different functions or having nested lambda and stuff. And, but just by using coroutines, this allows you to actually not block your main thread. I think this is the ultimate example that I have where that's all the support for QTCP server, QTCP socket where we have a wrapper for QTCP server where we can co-await new connection. So this will suspend the coroutine until a new connection is available. And then again, when the connection comes, it resumes the coroutine and then again waits until the socket has anything to read. And then ultimately it just reads the data and sends them back with punk before it. So it's a simple punk server. The thing is that this is a while loop, right? It's sort of a basically an endless while loop. And it seems like there's a lot of blocking by in fact, you could just put this code into your main thread and your application would not get blocked, right? Because whenever this needs to wait, the co-await will just suspend the coroutine and eventually chain up all the way to your event loop. All right, this is a list of all the classes that are supported right now. I showed some of them. There is also Q timer support so you can co-await the timer to timeout. You can co-await any arbitrary signal emission and there's a general support for QIO device as well. So that's that, that was a quick introduction to coroutines and Q Coro. If you are interested more in the Q Coro library, there is a github repository, github.com, slash that, or you can read the documentation here. Or if you have any questions about coroutines or the library, just hit me and email Twitter or matrix. Thank you. Let's all give Daniel a big round of applause. I see everyone's gonna be clapping in the chat. So going through some questions. Jan asks, have you tried debugging and reading stack traces of coroutines? So did you have a crash? What happened in GDB? Yes, it's horribly annoying because the support in the tooling is not there yet. So you see a random, there is a lot of generated code and generated frames and these are really hard to debug. Yeah, so debugging crashes are debugging crashes and memory leaks, especially that's annoying. Yeah. Okay, so annoying is the answer. So the next question, which I think you've just touched on at the end, is does this work out of the box for code is based on signals and slots like K-Job or do we need to do anything there to make it correlate compatible? You don't need to make, you don't need to modify the classes to make them compatible with coroutines. I did not patch anything in Qt. What you need to do is you need to introduce a wrapper and into the QCoro library possibly that can handle K-Job and then tell the coroutines machinery when to suspend, when to resume. The QCoro is written in a way that it should be possible to even add support for things like K-Job without having to patch QCoro even. You just need to provide some template specializations and it should work. So I haven't tried yet, but it should work. That sounds amazing. David is just writing my plan out. So I'll ask one more question. One of the biggest challenges of asynchronous code is managing a lifespan of all the objects that you're going to use in your callbacks and in your lambda and all your examples you used earlier like Python and Dart, they just represent everything. So it's a big problem with C++. How do you resolve this nicely in C++ and Qt? Well, this is what the coroutines really solve for you, because whenever you suspend the coroutine, the whole stack is saved to memory. So you don't need to, and then it's resumed, everything is restored, and then you use the regular, like whenever you return from the function, it destroys everything, right? So you suddenly can start allocating many things on stack because you don't have to bother, you don't have to be worried that your function will finish and then you need to capture everything into lambda, connect it to some signal or passing it to another function, right? So this actually, I think makes things not only much easier, but it also allows us to write much safer code because you can allocate everything on stack, so no memory leaks. Maybe user interfaces where people are closing things. Okay, we've got one more question and we've got time for it. Ingo asks, is it possible to cancel that update e.g. if the next title is needed from your Nougat or Debus? You can, well, if you have some other code that's executing that would cancel Debus message, then the message would be, the reply would finish and the coroutine would be resumed and then you would have to have some error handling. So in a way, yes, yes. So it's not a straight forward this code, the code I showed that doesn't really bother with error checking because errors don't happen, right? Okay, and how did this interact with a CPP-Coro compatibility or extension? I haven't tried. The problem is that I found a lot of inspiration in CPP-Coro, but the thing is it depends on the return type, right? So you cannot mix things from CPP-Coro and CPP-Coro in a single coroutine. So this will become a bit tricky, which is one of the weaknesses, I think, of the current approach to coroutines that you have to pick one library and then basically stick to it throughout your code base. Okay, one more question, I do keep coming in. And that's how did this relate to standard future and standard promise? I think you're touching this a little bit in your slides. Not at all, not at all. There is no threading involved, right? You can use threads, but you can call away the thread, but there is no threading in coroutines. Cool. And I assume you're on for our Academy to answer questions as they come in? Sure. Cool. Okay, and next talk is in three minutes. I will pass off to even to be a next session host. But please stay in room two. Thank you, bye. Unless you want to see a talk in room one, of course.