Flutter/Dart中如何实现函数执行完成后再周期性调用(避免并行)
Hey there! I totally get this frustration—Timer.periodic is great for simple periodic tasks, but it falls apart when your task takes longer than the interval, leading to parallel execution and those annoying variable race conditions. Streams are perfect for fixing this, and I’ll walk you through exactly how to implement what you need:
The Core Idea
Instead of triggering the task on a fixed timer regardless of completion, we want to run the task, wait 5 seconds after it finishes, then run it again. This ensures no parallel instances ever exist, and the minimum interval between task starts is always at least 5 seconds (plus whatever time the task itself takes).
Stream-Based Implementation
We can create a custom async stream that handles this sequential, delayed execution:
import 'dart:async'; // Define your interval as before const markerRefreshInterval = Duration(seconds: 5); // Create a stream that runs fetchMarkers sequentially with post-execution delay Stream<void> markerRefreshStream() async* { while (true) { try { print('Starting marker fetch...'); await _fetchMarkersFromDb(); // Wait for the task to complete fully print('Marker fetch completed successfully'); } catch (e, stackTrace) { // Handle errors without breaking the stream print('Error fetching markers: $e\nStack trace: $stackTrace'); } // Wait 5 seconds AFTER the task finishes before the next run print('Waiting 5 seconds before next fetch...'); await Future.delayed(markerRefreshInterval); } } // To start the stream (e.g., in initState) late StreamSubscription _refreshSubscription; @override void initState() { super.initState(); _refreshSubscription = markerRefreshStream().listen((_) {}); } // Don't forget to cancel the subscription to avoid memory leaks! @override void dispose() { _refreshSubscription.cancel(); super.dispose(); }
Why This Works
- No Parallel Execution: The
await _fetchMarkersFromDb()ensures we never start a new instance until the previous one has fully completed. - Guaranteed Minimum Interval: The
Future.delayedruns only after the task finishes, so the next task starts exactly 5 seconds after the last one ended. - Error Resilience: The
try/catchblock ensures a single failed fetch won’t stop the entire periodic process—your app will just log the error and keep going.
Why Timer.periodic Failed
Timer.periodic schedules a new callback every 5 seconds regardless of whether the previous callback finished. If _fetchMarkersFromDb() takes 6 seconds, you’ll end up with two instances running at the same time, causing race conditions on your incrementing variables. This stream approach eliminates that entirely.
内容的提问来源于stack exchange,提问作者Urmil Shroff

