Dart DISCUSSION

Dart async and await: why do I get an unresolved Future, and why does heavy work still freeze the UI?

Started by lumatetedavid Dart async awaitFutureevent loopisolatesFlutter UI jank
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

Dart async and await: why do I get an unresolved Future, and why does heavy work still freeze the UI?

lumatetedavid Dart Forum
#1

My function Future<String> readConfig() async loads a file. Calling print(readConfig()) prints Instance of 'Future<String>' instead of the text. After adding await that works, but a second function that parses a large log inside an async method still freezes my Flutter UI for about a second.

I assumed async meant "runs in the background". What do async and await really do, and how is CPU-heavy work supposed to be moved off the UI?

Community replies 5

Re: Dart async and await: why do I get an unresolved Future, and why does heavy work still freeze the UI?

#2

An async function returns immediately with a Future, an object that will complete with the value later. Printing it shows the Future itself. await suspends the current function until the Future completes and then yields the value: final text = await readConfig();.

It can only be used inside a function marked async, which therefore returns a Future as well, so asynchrony spreads up the call chain. The non-await alternative is a callback: readConfig().then((text) => print(text));.

Re: Dart async and await: why do I get an unresolved Future, and why does heavy work still freeze the UI?

#3

async does not create a thread. Dart code in an isolate runs on a single thread with an event loop. await does not block that thread; it returns control to the event loop, which handles other events (taps, frames, timers) and resumes your function when the awaited operation finishes. That is ideal for I/O, where the waiting is done by the operating system.

Parsing a big string is different: it is your own code using the CPU, and while it runs, nothing else on that thread can. Marking the function async changes nothing, because there is no point inside the loop where it hands control back.

Re: Dart async and await: why do I get an unresolved Future, and why does heavy work still freeze the UI?

#4

For CPU-bound work, use another isolate. final result = await Isolate.run(() => parseLog(text)); runs the function in a separate isolate with its own memory and returns the result as a Future; Flutter's compute(parseLog, text) does the same job. The UI isolate stays free to draw frames.

At 60 frames per second each frame has about 16 ms, so a task that takes one second blocks roughly 60 frames. Isolates do not share memory; the argument and the result are passed as messages, so keep them to plain data, and do not bother for work that takes well under a frame.

Re: Dart async and await: why do I get an unresolved Future, and why does heavy work still freeze the UI?

#5

Some behaviours that catch people out. An async function runs synchronously up to its first await, so heavy code before the first await still runs immediately in the caller's turn. list.forEach((x) async { await save(x); }) does not wait for anything: forEach ignores the returned Futures. Use a plain for loop with await for sequential work.

For independent operations, start them together: await Future.wait([a(), b(), c()]). Three requests of 2 seconds each then take about 2 seconds in total instead of 6.

Re: Dart async and await: why do I get an unresolved Future, and why does heavy work still freeze the UI?

#6

Errors travel through the Future. With await, a normal try/catch around the call catches them. If you call an async function without awaiting it and it fails, nobody is listening and the error surfaces as an unhandled exception far from the cause. Declare such functions as Future<void> rather than void, so callers can await them and the analyzer can warn about forgotten awaits.

In Flutter, create the Future once, for example in initState, and hand it to FutureBuilder. Calling readConfig() directly in build starts a new load on every rebuild.

TEP COMMUNITY