I tried to show a progress in angulardart, and thought that a Future would be good for this. But then i realized that a Future must be recursive to show a progress, since the Future returns immediately and the lengthy operation is executed afterwards.
If i create a Future that calls itself until the end condition is met it works with the progressbar. But i think this could not be a very good practice sind these calls will raise the memory on the stack with every recursion. Just consider a loop going through 1 billion datasets that could run a few hours and every loop calls a new Future within the current Future.
Is there a better way to create a loop that needs a certain amount of time to do work on every element (including calling a website that must be done asynchronous and evaluating the return value)? During the loop the user should see a progress that shows him "x/1000000 done".
I think it must be done with a Future since the UI needs to reload after initiating the loop, but a recursive Future seems like a bad idea to me.
You need the future to return back to you right away on the web because it is a single threaded platform. If an async action didn't return until it was complete then you would hang the browser and it wouldn't be a great experience to the user.
Instead you have a couple of options:
Dart has the ability to make the future look like it is synchronous with the await keyword. So you can do something like:
void performAction() async {
showProgress = true;
await expensiveRpc();
showProgress = false;
}
This would require the progress to be intermediate, as you aren't actually updating the progress bar as it goes along. That said if you don't really get progress events from your RPC this is probably the better solution.
Now if your RPC or action gives you some kind of feedback as it goes you can do something a bit nicer with a stream.
void performAction() {
showProgress = true;
expensiveRpc().listen((progress) {
if (progress.done) {
showProgress = false;
} else {
percentComplete = progress.value;
});
}
Really it depends more on the RPC or service you are interacting with on how you can update the progress nicely more than the progress itself.
Meanwhile i recognized that a Future-method returns immediately without executing anything in the method-body. So the solution is pretty easy:
Just declare the rpc with a Future, do whatever you need to do in the method and when calling it, use then(...) to do what you need to do after collecting the data.
int progress = 0;
int progressMax = 100;
bool progressCanceled = false;
Future rpc(var data)
async{
for(progress=0; progress<progressMax, progress++)
{
// do whatever you need to do with data
if(progressCanceled)
return;
}
}
rpc(data).then(
{
if(progressCanceled)
return;
// do whatever is needed after having received that data
});
rpc is executed and the calling process can continue while rpc does what rpc has to do. The main program can handle button clicks to set progressCanceled to true and the rpc-method will ask for the state and stop processing if it is set.
Related
I have watched Explore structured concurrency in Swift video and other relevant videos / articles / books I was able to find (swift by Sundell, hacking with swift, Ray Renderlich), but all examples there are very trivial - async functions usually only have 1 async call in them. How should this work in real life code?
For example:
...
task = Task {
var longRunningWorker: LongRunningWorker? = nil
do {
var fileURL = state.fileURL
if state.needsCompression {
longRunningWorker = LongRunningWorker(inputURL: fileURL)
fileURL = try await longRunningWorker!.doAsyncWork()
}
let urls = try await ApiService.i.fetchUploadUrls()
if let image = state.image, let imageData = image.jpegData(compressionQuality: 0.8) {
guard let imageUrl = urls.signedImageUrl else {
fatalError("Cover art supplied but art upload URL is nil")
}
try await ApiService.i.uploadData(url: imageUrl, data: imageData)
}
let fileData = try Data(contentsOf: state.fileUrl)
try await ApiService.i.uploadData(url: urls.signedFileUrl, data: fileData)
try await ApiService.i.doAnotherAsyncNetworkCall()
} catch {
longRunningWorker?.deleteFilesIfNecessary()
throw error
}
}
...
Then at some point I will call task.cancel().
Whose responsible for cancelling what? Examples I've seen so far would use try Task.checkCancellation(), but for this code that line should appear every few lines - is that how it should be done?
If API service uses URLSession the calls will be cancelled on iOS 15, but we don't use async variant of URLSession code so we have to cancel the calls manually. Also this applies to all the long running worker code.
I am also thinking that I could add this check within each of async functions, but then basically all async functions would have the same boilerplate code which again seems wrong and I haven't seen that done in any of the videos.
EDIT:
I have removed callback calls as those are irrelevant to the question.
There are two basic patterns for the implementation of our own cancelation logic:
Use withTaskCancellationHandler(operation:onCancel:) to wrap your cancelable asynchronous process.
This is useful when calling a cancelable legacy API and wrapping it in a Task. This way, canceling a task can proactively stop the asynchronous process in your legacy API, rather than waiting until you reach a manual isCancelled or checkCancellation call. This pattern works well with iOS 13/14 URLSession API, or any asynchronous API that offers a cancelation method.
Periodically check isCancelled or try checkCancellation.
This is useful in scenarios where you are performing some manual, computationally intensive process with a loop.
Many discussions about handling cooperative cancelation tend to dwell on these methods, but when dealing with legacy cancelable API, the aforementioned withTaskCancellationHandler is generally the better solution.
So, I would personally focus on implementing cooperative cancelation in your methods that wrap some legacy asynchronous process. And generally the cancelation logic will percolate up, frequently not requiring additional checking further up in the call chain, often handled by whatever error handling logic you might already have.
Examples I've seen so far would use try Task.checkCancellation(), but for this code that line should appear every few lines - is that how it should be done?
Basically yes. Cancellation is a totally voluntary venture. The runtime doesn't know what cancellation means for your particular task, so it just leaves it up to you. You look at Task.isCancelled, or, if your intention is to throw just in case the task is cancelled, you can call Task.checkCancellation.
Note that if, within your task, you are calling (with try) any async material that throws when cancelled, you do not need to any cancellation work with regard to that material, because when it throws due to cancellation, you will throw due to cancellation automatically.
Having said all that, I have to add, as a footnote, that your code is extremely strange. Callbacks and async/await are opposites; the idea that you would do a do/catch and call a callback within a Task is extremely weird and I would advise against it. You are basically negating all the advantages of a Task by doing that, as well as making untrue the thing I just said about the throw trickling up and out of your task.
Is it possible to make future somehow like a re-runnable task? For example, if I have to made a network call using a future and it failed for authentication reason. I would like to re-run the network call future once auth succeeded. How can I do that?
My expected code would probably look similar to this
Future task = fetchData();
Future handleService(task) async {
try {
final data = await task;
return data;
} catch (ex) {
// requires authentication
if(ex.code == 202) {
bool authSuccess = await reAuth();
if (authSuccess) {
await task
}
}
}
}
Simple answer: you can't re-run a Future.
Future can be completed only once. Moreover, Future represents the result of an async computation. I think about it that way: you run a task that returns a token (Future). When the tasks comletes, it sets the value on the Future.
On top of that, Future can have its value set only once, it cannot be completed with 2 different values (even by the task whose result it represents) Once a value is set, it will always hold the same one, and not allow modification.
In your case, you need to call fetchData again.
If you have a function that may return multiple values, you can use a Stream, but this approach doesn't fit your problem.
I would like to launch a fairly expensive operation in response to a user clicking on a canvas element.
mouseDown(MouseEvent e) {
print("entering event handler");
var future = new Future<int>(expensiveFunction);
future.then((int value) => redrawCanvas(value);
print("done event handler");
}
expensiveFunction() {
for(int i = 0; i < 1000000000; i++){
//do something insane here
}
}
redrawCanvas(int value) {
//do stuff here
print("redrawing canvas");
}
My understanding of M4 Dart, is that this future constructor should launch "expensiveFunction" asynchronously, aka on a different thread from the main one. And it does appear this way, as "done event handler" is immediately printed into my output window in the IDE, and then some time later "redrawing canvas" is printed. However, if I click on the element again nothing happens until my "expensiveFunction" is done running from the previous click.
How do I use futures to simply launch an compute intensive function on new thread such that I can have multiple of them queued up in response to multiple clicks, even if the first future is not complete yet?
Thanks.
As mentioned in a different answer, Futures are just a "placeholder for a value that is made available in the future". They don't necessarily imply concurrency.
Dart has a concept of isolates for concurrency. You can spawn an isolate to run some code in a parallel thread or process.
dart2js can compile isolates into Web Workers. A Web Worker can run in a separate thread.
Try something like this:
import 'dart:isolate';
expensiveOperation(SendPort replyTo) {
var result = doExpensiveThing(msg);
replyTo.send(result);
}
main() async {
var receive = new ReceivePort();
var isolate = await Isolate.spawn(expensiveOperation, receive.sendPort);
var result = await receive.first;
print(result);
}
(I haven't tested the above, but something like it should work.)
Event Loop & Event Queue
You should note that Futures are not threads. They do not run concurrently, and in fact, Dart is single-threaded. All Dart code runs in an event loop.
The event loop is a loop that runs as long as the current Dart isolate is alive. When you call main() to start a Dart application, the isolate is created, and it is no longer alive after the main method is completed and all items on the event queue are completed as well.
The event queue is the set of all functions that still need to finish executing. Because Dart is single threaded, all of these functions need to run one at a time. So when one item in the event queue is completed, another one begins. The exact timing and scheduling of the event queue is something that's way more complicated than I can explain myself.
Therefore, asynchronous processing is important to prevent the single thread from being blocked by some long running execution. In a UI, a long process can cause visual jankiness and hinder your app.
Futures
Futures represent a value that will be available sometime in the Future, hence the name. When a Future is created, it is returned immediately, and execution continues.
The callback associated with that Future (in your case, expensiveFunction) is "started" by being added to the event queue. When you return from the current isolate, the callback runs and as soon as it can, the code after then.
Streams
Because your Futures are by definition asynchronous, and you don't know when they return, you want to queue up your callbacks so that they remain in order.
A Stream is an object that emits events that can be subscribed to. When you write canvasElement.onClick.listen(...) you are asking for the onClick Stream of MouseEvents, which you then subscribe to with listen.
You can use Streams to queue up events and register a callback on those events to run the code you'd like.
What to Write
main() {
// Used to add events to a stream.
var controller = new StreamController<Future>();
// Pause when we get an event so that we take one value at a time.
var subscription = controller.stream.listen(
(_) => subscription.pause());
var canvas = new CanvasElement();
canvas.onClick.listen((MouseEvent e) {
print("entering event handler");
var future = new Future<int>(expensiveFunction);
// Resume subscription after our callback is called.
controller.add(future.then(redrawCanvas).then(subscription.resume()));
print("done event handler");
});
}
expensiveFunction() {
for(int i = 0; i < 1000000000; i++){
//do something insane here
}
}
redrawCanvas(int value) {
//do stuff here
print("redrawing canvas");
}
Here we are queuing up our redrawCanvas callbacks by pausing after each mouse click, and then resuming after redrawCanvas has been called.
More Information
See also this great answer to a similar question.
A great place to start reading about Dart's asynchrony is the first part of this article about the dart:io library and this article about the dart:async library.
For more information about Futures, see this article about Futures.
For Streams information, see this article about adding to Streams and this article about creating Streams.
My use case is that whenever an user types something an EditText, the input data is used for performing operations on the background. These operations might take long enough to cause an ANR. #TextChange together with #Background works fine if the operation is done quicly enough. But is the operation takes long enough, so that the user inputs more data, I will get threading issues as there will be multiple background tasks that will command the update of same UI component.
I think I achieve the wanted behaviour with AsyncTask API, but wanted to look for AndroidAnnotations based solutions as well, as it simplifies the code a lot. Great lib by the way.
Below are some code snippets that'll hopefully illustrate my point. Thanks for at least reading, comments/answers appreciated :)
#TextChange
void onUserInput(...) {
// This will start a new thread on each text change event
// thus leading to a situation that the thread finishing
// last will update the ui
// the operation time is not fixed so the last event is
// not necessary the last thread that finished
doOperation()
}
#Background
void doOperation() {
// Sleep to simulate long taking operation
Thread.sleep( 6000 );
updateUi()
}
#UiThread
void updateUi() {
// Update text field etc content based on operations
}
UPDATE: This is not possible at the moment, see DayS' answer below.
It's possible since AA 3.0. Read this thread.
#Override
protected void onStop() {
super.onStop();
boolean mayInterruptIfRunning = true;
BackgroundExecutor.cancelAll("longtask", mayInterruptIfRunning);
}
#Background(id="longtask")
public void doSomethingLong() {
// ...
}
There already was this kind of request on Android Annotations but it was closed because no solution was proposed. But if you have any idea about it, go ahead and re-open this issue ;)
So here's my problem. I have code set up that calls a function whenever my player is over its last destination in the a* pathfinding array...
public function rakeSoil(e:Event):void {
var:Cell = Grid.getCellAt(player.x/50, player.y/50);
if (cell.isWalkable == false) {
return;
else {
//here is where i want to do the sleep code so this doesnt happen straight away? If possible.
target.sprites = [grass];
}
}
thanks guys :)
Generally, the "right" way to delay execution of something is to use a Timer.
Hacking up some kind of a sleep function could cause problems, since Flash runs in a single thread, so you won't be able to do anything else while your function is running, including refreshing the screen (making your game appear as if it crashed, or at least started lagging).
If you're absolutely, positively sure you want to do this, you could call the getTimer() function in a loop to see if a certain amount of miliseconds has passed.