Solved ObservableFinished() does not pass a job result to an observer

Hi everyone,

I would like to use Cinemas job system to send multiple HTTP requests asynchronous / in parallel. I have created a custom HttpRequestJob which will return a custom HttpResponse struct.

After the job will be enqueued (and therefore started) I can sucessfully ask for the HTTP response with:

HttpResponse response = httpRequestJob.MoveResult() iferr_return;

Instead of asking the job for a result I would like to get a callback function called after the job has finished, because this would better fit into our current plugin architecture. I would like to add an observer to httpRequestJob.ObservableFinished().

The problem is:
I will not get the result from the job (an HttpResponse object) passed to the observer / callback function. The callback function has the following signature:

static void RequestCallback(HttpResponse httpResponse)

It seems the following does not work because the callback function has a parameter:

httpRequestJob.ObservableFinished().AddObserver(RequestCallback);

It also does not work when I provide a lambda with a HttpResponse or const HttpResponse& argument as an observer:

httpRequestJob.ObservableFinished().AddObserver([](HttpResponse response)
{
   // Doing something with the response ...
}) iferr_return;

It seems AddObserver() will only take a callback function without arguments in this case, but this way I cannot do any meaningful work with the observer.

Any ideas how I can get access to the job result in an observer? Thanks in advance!

Best regards,

Tim

PS: The job looks something like this:

class HttpRequestJob : public maxon::JobInterfaceTemplate<HttpRequestJob, HttpResponse>
{
public:
	HttpRequestJob() { };

	HttpRequestJob(
		const String &url,
		const String &method,
		const String &body,
		const maxon::DataDictionary &headers)
	{
		_url = url;
		_method = method;
		_body = body;
		_headers = headers;
	}

	maxon::Result<void> operator ()()
	{
		iferr (HttpResponse httpResponse = SendHttpRequest(_url, _method, _body, _headers, _description))
		{
			ApplicationOutput("@", err);
			return err;
		}

		return SetResult(std::move(httpResponse));
	}

	const maxon::Char* GetName() const
	{
		return "HttpRequestJob";
	}

private:
	maxon::String _url;
	maxon::String _method;
	maxon::String _body;
	maxon::DataDictionary _headers;
};

hi,

You can add a lambda and capture this so you will have access to your job's class. as if you were on the same part of your code.
This doesn't answer the question if you need to pass a static function with a parameter. For that i would need a bit more searching and test.
Also you can simply call the function you want with the parameter you want from the lambda.

httpRequestJob.ObservableFinished().AddObserver([this]()
{
	iferr_scope;
   	HttpResponse result =  httpRequestJob.GetResult() iferr_return
        ...

}) iferr_return;

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Thanks for your reply Manuel (@m_magalhaes)!

One of my first ideas for a solution was to use a this capture for the lambda, but in my case the function which creates the HttpRequestJob and adds the Observer is a static function and I got the following compiler error: 'this' can only be used as a lambda capture within a non-static member function.
So I replaced the this capture with an & capture:

httpRequestJob.ObservableFinished().AddObserver([&]()
{
	iferr (HttpResponse response = httpRequestJob.GetResult())
	{
		ApplicationOutput("Error: @", err);
	}

        ...

}) iferr_return;

This will compile, but GetResult() returns the following error at runtime: nullptr [job.h(1354)] The returned HttpResponse will be empty / uninitialized. It seems the result might not exist anymore when the observer gets called. httpRequestJob.GetResult() works fine if called outside of the lambda, so I had the feeling that getting the result inside the lambda was not the right way to go. I was expecting that ObservableFinished() would pass the result instead.

Any more ideas?

Best regards,
Tim

hi,

sorry for the delay, i was trying different solutions.

I simply forgot the simple one.

In fact, in your lambda, when capturing scope variable by reference with & it's not working because once the job have finished, the reference count go to 0 and the job is destroyed.
Instead, if you pass the job as "copy" the reference count will rise by one so the job will not be destroyed. Instead it will be destroyed once the lambda is finished.
So you can retrieve the result and maybe send it to another function or do something with it.

struct HttpResponse 
{
	Int32 firstArgument = NOTOK;
	Int32 secondArgument = NOTOK;
};


class HttpRequestJob : public maxon::JobInterfaceTemplate<HttpRequestJob, HttpResponse>
{
public:
	HttpRequestJob() { };

	MAXON_IMPLICIT HttpRequestJob(const Int32 in_first, const Int32 in_second) : first(in_first), second(in_second) {};
	
	maxon::Result<void> operator ()()
	{
		iferr_scope;
		HttpResponse httpResponse{ first * second, second * first };
		return SetResult(std::move(httpResponse));
	}

	const maxon::Char* GetName() const
	{
		return "HttpRequestJob";
	}

private:
	Int32 first = NOTOK;
	Int32 second = NOTOK;
};


static maxon::Result<void> pc12926(BaseDocument* doc)
{
	iferr_scope;

	for (Int32 i = 0; i < 10; i++)
	{
		auto connection = HttpRequestJob::Create(i, i + 10) iferr_return;
		connection.ObservableFinished().AddObserver([connection]() -> maxon::Result<void>
			{
				iferr_scope;
				HttpResponse res = connection.GetResult() iferr_return;
				ApplicationOutput("result are @ and @", res.firstArgument, res.secondArgument);
				return maxon::OK;
			})iferr_return;

		connection.Enqueue();
	}
	
	return maxon::OK;
}

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Thank you very much, Manuel!
This is a working solution for the issue and it helped me a lot!

Best regards,
Tim