-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configurable async runtime #23
base: main
Are you sure you want to change the base?
Conversation
Thanks, most of this looks good to me.
Well as also intended in xilem, the view function/app-logic and the widget-tree run on their own thread (widget-tree on the main thread) because either of them could become CPU-bound (the risk in a TUI context is much lower though admittedly). I would prefer to keep them in their own threads (not just to stay in sync with xilem itself) as they're long-running tasks too. I'm not sure about event-handling, I guess this could be done in the tokio runtime, but generally I think it makes sense to me to have a physical thread for that (to stay responsive). Regarding the waker thread, this should be its own OS thread because the
That should not be too bad, when the only thing a thread should do is e.g. waiting for incoming events.
Sure that should happen anyway. A good example (and something that is planned for either xilem as well as for trui) is the async list on the |
Thanks a lot. That's great feedback. I was expecting some of that because I assumed the code was this way for a reason, but not why. I haven't checked in detail yet in the Xilem-web implementation, but as far as I am aware threads aren't well supported in WASM yet. Is this all covered by browser functionality there? |
Well part of why I'm contributing to xilem etc. is because of the lack of control over such things. Not true of course for everything, as the browser could offload work to threads (gpu stuff, hardware accelerated things), but for the user it's mostly that. I have had some thoughts about off-loading view-rendering onto a web-worker, but I think there's more important stuff to do (as this also adds quite a bit of complexity), and after my (not so comprehensive) benchmarks it's also not really necessary (needs more real-life apps/testing though). I think |
It was a bit late for me already yesterday. There are a few more things I'm interested in. I hope you don't mind.
I'm a bit stuck with the approach to offload to another thread when there actually is CPU-bound work, not to create a bunch of threads because something could become CPU-bound. Especially with app-logic. Event handling and also most network requests just doesn't need any notable CPU resources. If there actually is some real work like decompressing a video, calculating complex 3D models, ..., then that should be done in an additional thread. I'll try to wrap my head around the current approach. Perhaps I'm just biased from previous work.
Staying in sync with Xilem is a good argument.
That was actually the main point that made my try the full async route. But my problem was the other way. My first thought was Posix signal handling.
It's a bit hard for me to see how event handling could be even a burden for an async runtime to cause responsivity issues. I'll try to wrap my head around it.
Thanks for that. I thought this shouldn't work, but I only tested the animations example and it did work, which I found confusing.
By itself this isn't bad at all. This was just the "use case", when I wanted to use async outside
I'll definitely look into all that. Thanks for the pointers. |
When the main thread waits for information what to render from the web worker, it usually doesn't have anything else to do anyway.
A sane language to work with was my main reason :D. I like what you did with xilem_web and trui. I prefer to build the view multiple times, optimized for each target platform, and be able to fully utilize each targets capabilities. (it's nice of course if some parts can be reused). |
It's less the event-handling, more other async tasks that take more time (e.g. view reconciliation) and block incoming events. But I doubt that it will be really an issue, as I said I'm not sure... I guess we can do that async.
Either works, and I think we won't really see a difference in practice. It's just that I generally like to avoid async (because of all the complexity it comes with) for anything where a thread certainly also makes sense (when it's facing the user at least).
I just doubt that this will ever be a real-world limitation. When it's just for tests, we should find workarounds.
Sure that's also a way (or
Well the argument behind it would be that, the diffing etc. doesn't really need to run on the main thread, and instead could generate a change stream which the main thread handles. The main thread is doing all sorts of stuff in the "background" (e.g. when you're scrolling it could hang, when some task takes too much time), so I think it's better to do as little as possible there (DOM operations unfortunately need to be done there).
Yeah I also came to that conclusion (after going the other route and hitting limitations a few times...).
Oh wow, didn't know that. I haven't looked to deep into Dioxus TUI, because I think HTML/CSS is not really a good fit for TUI.
Yeah that's an active research area. I'm wrapping my head around for some time now how to do async services and easily mutate the state necessary for the view in a nice way, without being to bound on the view (I currently think using the MessageResult in the view may be a way to do this).
The animations run on the main thread (via a lifecycle pass currently), the async stuff on the tokio runtime. |
I just think that this should probably be done differently (the exact semantics is as said still a research area) |
The workaround for tests is working. I just wanted to find out if and how it can be improved for real world code.
This surprises me a bit because it's already in use. I read that article recently. It's quite a journey :D I don't think the keyword generics approach will succeed for async (and probably also not the other variants). Sync and async are just too different.
Do you have a link to some code where this is used? I feel like I stumbled upon it already somewhere but I don't remember. My best experience is with Redux. The central loop that feeds the view is sync. All async parts are separated and feed the results into the sync loop when they become available. Async for network requests, file access and all other I/O stuff is really neat when you have for example Rx available (RxRust looks quite good already), especially for complex code like coordinating multiple network requests with retries, timeouts, caching, error handling. It's just when devs try to do too much in the view then async becomes relevant there and then complexity grows fast. Sadly this seems to be the main tendency nowadays. I don't know enough yet to even make concrete suggestions for Xilem/Trui. I just try to understand and figure out what works or how it can be made to work. Another question: |
Well the TODO is not from me (I suspect Raph). I don't consider it at least immediate priority currently. I'm not really sure, but I guess to give the user more control?
As I said, it's weighing between pros and cons. I think it's certainly ok to have views supporting async (as said something like the async list or async loading views etc.) and of course internally. Just that it should not be a requirement to be forced to use async (user-facing).
Sure, async should not be avoided when it really makes sense, but I don't think this is really the case here. For reference ratatuis API is also sync (where it might make sense to have it async). So right now I don't see a real reason why the main thread should be async, apart from the rendering (and even that takes some CPU for diffing the buffers currently) and maybe waiting for input events, it's not really IO bound. In the future there will likely happen more on the main thread (fancier layout etc.). But I doubt we'll really run into any limitations with either approach, TUI is not that demanding.
For example Something like (where async_service(my_async_service, |app_state, message: Message| {
match message {
Message::UpdateMyOneState(one) => app_state.one = one,
Message::UpdateMyOtherState(other) => app_state.other = other,
}
})
|
This topic is off the table now anyway. I just wanted to understand the reasoning.
Ok, I'll look into that as well |
I initially just wanted to clean up
app.rs
a bit,like the TODO about configurable runtime, the many conditionals for test code and also the initialization of tracing that shouldn't be hardcoded in a library.
Then I tried to run with just the
#[tokio::main]
attribute to verify my changes. I run into many blocking send's and recv's.I thought that initializing a async runtime using
#[tokio::main]
would be the main use case. So I tried to make it work. That led to changing everything to async to avoid the blocking.I also could have introduced another thread somewhere to make the original code work again.
I'm not sure if I'm missing something, but to me it looks like there is not really any CPU bound workload, but almost only I/O processing, so fully utilizing async would make sense to me. I think if there is some CPU-heavy work to do, it would be ideal to just offload these parts to additional threads.
(Re)Creating the views and diffing the trees is some actual CPU work, but my assumption is, that this is not enough to saturate a thread, and if, this specifically should be offloaded to additional threads.
The PR currently is just a basis for discussion and feedback and needs a bit more work.
Especially the async runtime in
AppConfig
and theHandle
's derived from it seem redundant to me. I think just using the runtime from the context should be enough (without any configuration option).If this goes against your plans or goals, I'm happy to learn and to choose a different approach.
(modified to fix typo)