Inout Parameter in Async Callback Does Not Work as Expected

Inout parameter in async callback does not work as expected

Sad to say, modifying inout parameter in async-callback is meaningless.

From the official document:

Parameters can provide default values to simplify function calls and can be passed as in-out parameters, which modify a passed variable once the function has completed its execution.

...

An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value.

Semantically, in-out parameter is not "call-by-reference", but "call-by-copy-restore".

In your case, counter is write-backed only when getOneUserApiData() returns, not in dataTaskWithRequest() callback.

Here is what happened in your code

  1. at getOneUserApiData() call, the value of counter 0 copied to c1
  2. the closure captures c1
  3. call dataTaskWithRequest()
  4. getOneUserApiData returns, and the value of - unmodified - c1 is write-backed to counter
  5. repeat 1-4 procedure for c2, c3, c4 ...
  6. ... fetching from the Internet ...
  7. callback is called and c1 is incremented.
  8. callback is called and c2 is incremented.
  9. callback is called and c3 is incremented.
  10. callback is called and c4 is incremented.
  11. ...

As a result counter is unmodified :(


Detailed explaination

Normally, in-out parameter is passed by reference, but it's just a result of compiler optimization. When closure captures inout parameter, "pass-by-reference" is not safe, because the compiler cannot guarantee the lifetime of the original value. For example, consider the following code:

func foo() -> () -> Void {
var i = 0
return bar(&i)
}

func bar(inout x:Int) -> () -> Void {
return {
x++
return
}
}

let closure = foo()
closure()

In this code, var i is freed when foo() returns. If x is a reference to i, x++ causes access violation. To prevent such race condition, Swift adopts "call-by-copy-restore" strategy here.

Swift inout not getting set

You need to pass a 'continuation' to your downloadImage() function; the continuation is what to do with the downloaded image: Like this:

func downloadImage(url: String?, cont: ((UIImage?) -> Void)?) {
if url != nil {

var imgURL: NSURL = NSURL(string: url!)!
var request: NSURLRequest = NSURLRequest(URL: imgURL)

NSURLConnection.sendAsynchronousRequest (request,
queue:NSOperationQueue.mainQueue(),
completionHandler: {
(response: NSURLResponse!, data: NSData!,error: NSError!) -> Void in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
// No point making an image unless the 'cont' exists
cont?(UIImage(data: data))
return
})
}
else { println ("Error") }
})
}
}

and then you use it like this:

var theImage : UIImage?

downloadImage ("https://imgur.com/Y6yQQGY") { (image:UIImage?) in
theImage = image
}

where I've exploited syntax for a trailing closure and simply assign the optional, downloaded image to the desired variable.

Also, please note that I have not examined your thread structure; it may be that the cont function should be called on some other queue - but, you get the point of passing in a continuation.

Mutate inout function paramter inside async block

For some theory on the inout keyword, see the following answer:

  • When to use inout parameters?

Do not depend on the behavioral differences between copy-in copy-out
and call by reference.

...

When the function returns, your changes to the original are
overwritten with the value of the copy
. Do not depend on the
implementation of the call-by-reference optimization to try to keep
the changes from being overwritten.

Now, your addItem function will finish its call immediately and hence complete the inout copy-in/copy-out task prior to the delayed dispatch in the function. This makes it inherently bad to use delayed dispatches within a method of inout parameters, at least if the delay is one that tries to mutate the inout parameter.

To see this, lets track the references (rather than tracking the values) of some array, and how they possibly show mutation of the array during the runtime of our example.

func foo(inout bar: [Int]) {
var pBar : UnsafePointer<Int> = UnsafePointer(bar)
print("2: \(pBar)")
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
pBar = UnsafePointer(bar)
print("3: \(pBar)")
bar[0] = 2
pBar = UnsafePointer(bar)
print("4: \(pBar)")
}
}

var a : [Int] = [1]
var pA : UnsafePointer<Int> = UnsafePointer(a)
print("1: \(pA)")
foo(&a)
print("foo call to a finished, a = \(a)")

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(5 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
print("value of a naturally not changed here = \(a)")
pA = UnsafePointer(a)
print("5: \(pA)")
}

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

The output is quite self-explanatory:

1: 0x00007fe19271e930
2: 0x00007fe19271e930
foo call to a finished, a = [1] <-- call to foo finished, 'inout' procedure complete
3: 0x00007fe19271e930 <-- dispatch in `foo` starts
4: 0x00007fe1927085e0 <-- mutates 'bar': 'bar' copied (and never
"returned" as this step is already finished)
value of a naturally not changed here = [1]
5: 0x00007fe19271e930 <-- naturally 'a' wont see the effect of the
delayed mutation in foo

I have a node async function example. Callbacks returning not expected values

It's because your for loop has already completed by the time the first async callback occurs so i is 11 at that point which is why that gets logged for the if (error) path that the odd numbers take.

How do I pass an async callback containing an argument that is a reference?

A callback argument passed as reference does not work with HRTB constraints when the callback is marked with the async keyword.

The signature using async/await:

async fn handle_request<'a>(request: Request, body: &'a mut (dyn AsyncRead + 'a)) -> HandlerResponse

Is equivalent to:

fn handle_request<'a>(request: Request, body: &'a mut (dyn AsyncRead + 'a)) -> Future<Output=HandlerResponse> + 'a

This implies that input lifetimes of an async function are captured in the
future returned by the async function.

See the paragraph "Lifetime capture in the anonymous future" of RFC 2394.

Declaring the function that accepts the parameter as:

pub async fn process_requests<H, F>(
mut connection: Box<dyn AsyncConnection>,
request_handler: &H,
) -> Result<(), DecodeError>
where
for<'a> H: Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) -> F + 'a,
F: Future<Output = HandlerResponse>,
{

Give a compilation error because the HRTB requirement:

for<'a> H: Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) -> F + 'a

"unlink" the lifetime bound from the caller and produce the compilation error
expected bound lifetime parameter 'a, found concrete lifetime

for more details about HRTB see here.

To make it works you have to write:

pub async fn process_requests<'a, H, F>(
mut connection: Box<dyn AsyncConnection>,
request_handler: &H,
) -> Result<(), DecodeError>
where
H: Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) -> F + 'a,
F: Future<Output = HandlerResponse>,
{

But this get you to another problem:

`body` does not live long enough

because the local body struct does not outlive request_handler:

async fn handle_request<'a, H, F>(
request_handler: &H,
request: Request,
) -> io::Result<()>
where
H: Fn(Request, &'a mut (dyn AsyncRead + 'a)) -> F,
F: Future<Output = String>,
{
let mut body = Body {};
request_handler(request, &mut body);
unimplemented!();
}

If feasible
one possible solution could be to use Boxed trait objects and get rid off HTRB constraints.

Function that takes an async callback and puts it in a FuturesUnordered queue

I don't think you need Pin, Box, or even references, for that matter.

You own the Vec, so just take elements out of it via move. That transfers the ownership of the elements to the f callback, which makes ownership management a lot easier.

use async_stream::stream;
use futures::stream::{FuturesUnordered, StreamExt};

pub fn map<U, W>(f: impl Fn(U) -> W, items: Vec<U>) -> impl futures::Stream<Item = W::Output>
where
W: futures::Future + Send + 'static,
W::Output: Send,
{
stream! {
// Convert into a fused iterator. Fused iterators
// are guaranteed to return `None` continuously after
// their last item.
let mut iter = items.into_iter().fuse();
let mut futures = FuturesUnordered::new();

if let Some(el) = iter.next() {
futures.push(tokio::spawn(f(el)));
}
if let Some(el) = iter.next() {
futures.push(tokio::spawn(f(el)));
}

while let Some(result) = futures.next().await {
let y = result.unwrap();
yield y;

if let Some(el) = iter.next() {
futures.push(tokio::spawn(f(el)));
}
}
}
}

#[tokio::main]
async fn main() {
async fn f(x: u32) -> u32 {
x + 1
}
let input = vec![1, 2, 3];
let output = map(f, input);
futures::pin_mut!(output);
while let Some(x) = output.next().await {
println!("{:?}", x);
}
}
2
3
4

I had to rewrite parts of your code because your version didn't have a stop criterium. It would always just crash by running out-of-bounds on the array access.

How to write an async method with out parameter?

You can't have async methods with ref or out parameters.

Lucian Wischik explains why this is not possible on this MSDN thread: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have-ref-or-out-parameters

As for why async methods don't support out-by-reference parameters?
(or ref parameters?) That's a limitation of the CLR. We chose to
implement async methods in a similar way to iterator methods -- i.e.
through the compiler transforming the method into a
state-machine-object. The CLR has no safe way to store the address of
an "out parameter" or "reference parameter" as a field of an object.
The only way to have supported out-by-reference parameters would be if
the async feature were done by a low-level CLR rewrite instead of a
compiler-rewrite. We examined that approach, and it had a lot going
for it, but it would ultimately have been so costly that it'd never
have happened.

A typical workaround for this situation is to have the async method return a Tuple instead.
You could re-write your method as such:

public async Task Method1()
{
var tuple = await GetDataTaskAsync();
int op = tuple.Item1;
int result = tuple.Item2;
}

public async Task<Tuple<int, int>> GetDataTaskAsync()
{
//...
return new Tuple<int, int>(1, 2);
}


Related Topics



Leave a reply



Submit