contextfree / winrt-rust

Use and (eventually) make Windows Runtime APIs with Rust
Apache License 2.0
142 stars 10 forks source link

Why does AsyncOperationCompletedHandler::new require Send? #63

Open chris-morgan opened 5 years ago

chris-morgan commented 5 years ago

AsyncOperationCompletedHandler::new::<_F_> includes Send in its bounds for _F_, and I’m not sure why it does, or whether it should in fact.

For “I’m not sure why”: AsyncOperationCompletedHandler is explicitly marked !Send and !Sync, so why would its callback need to be Send?

For “I’m not sure whether it should”: my concrete use case is WebViewControl, which requires a single-threaded apartment anyway. Given that WebViewControl instantiation is done asynchronously and you can’t use blocking_get() for reasons I won’t contemplate, the Send bound on the callback is very debilitating; it actively blocks what seems to me the most likely way you want to use it.

I have not investigated in any depth, but my intuition says that IAsyncOperation would always be at least effectively single-threaded.

Have I missed something? Is there a reason why that Send bound is there?

Boddlnagg commented 5 years ago

Short answer: I do not know.

I think that I added the Send bound to all those callbacks just as a precursory safety measure. It's possible that it's totally unnecessary. I would like to think more about all this async stuff anyway, because I'd like to see if it can be combined with Rust's new async/await feature. I could really need some helping hands there, though :-)

chris-morgan commented 5 years ago

I was thinking a similar thing about whether it could be handled in async/await. I haven’t thought too hard about it yet.

I can’t speak for other uses of it, but I can now speak for the WebViewControl case: you set the Completed handler, and it will then be called later from the same thread in a DispatchMessageW (i.e. you need to get an event loop running).

For my particular case, I believe I need the Send bound not to be present. (I’ve worked around this so far with an unsafe wrapper, and on reflection may continue to need to do something like that anyway.) I picture the code normally going something like this:

let mut control = None;
let operation = process
    .create_web_view_control_async(host_window_handle, rect)
    .expect("CreateWebViewControlAsync call failed");
operation.set_completed(&AsyncOperationCompletedHandler::new(|_sender, _args| {
    control = operation.get_results().unwrap();
    if let Some(ref control) = control {
        control.navigate(&Uri::create_uri(&FastHString::from("http://www.example.com")).unwrap()).unwrap();
    }
}));
Boddlnagg commented 5 years ago

We would really need to know if it's guaranteed that async operations always execute on the thread that created the callback. If it's not guaranteed, the Send bound is necessary, but we could add some unsafe way to create handler with a !Send callback.

By the way, I'm experimenting with async/await in a branch: https://github.com/Boddlnagg/winrt-rust/tree/futures-preview