Skip to content

Blocking the platform thread from a plugin causes UI to freeze #22024

Closed

Description

@cbenhagen

According to the wiki "...blocking the platform thread for inordinate amounts of time will not cause jank in Flutter applications,...".

Imagine a plugin with the following methods:

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([@"sleep" isEqualToString:call.method]) {
    [NSThread sleepForTimeInterval:10.0f];
    result(@"Good morning!");
  } else {
    result(FlutterMethodNotImplemented);
  }
}
  @Override
  public void onMethodCall(MethodCall call, Result result) {
    if (call.method.equals("sleep")) {
      try {
        Thread.sleep(10000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      result.success("Good morning!");
    } else {
      result.notImplemented();
    }
  }

Calling these methods from Flutter causes the UI to freeze completely. As this happens both on Android and iOS this must be either by design or a bug. A full example project can be found here: https://github.com/cbenhagen/sleepy

[✓] Flutter (Channel dev, v0.9.1, on Mac OS X 10.14 18A389, locale de-CH)
    • Flutter version 0.9.1 at /Users/ben/flutter
    • Framework revision bf7c27095c (13 hours ago), 2018-09-18 13:39:21 -0700
    • Engine revision 9e315e6465
    • Dart version 2.1.0-dev.4.0.flutter-050561fd82

[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
    • Android SDK at /Users/ben/Library/Android/sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-27, build-tools 27.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)
    • All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 10.0)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 10.0, Build version 10A254a
    • ios-deploy 2.0.0
    • CocoaPods version 1.5.3

[✓] Android Studio (version 3.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 28.0.1
    • Dart plugin version 173.4700
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)

[✓] Connected devices (2 available)
    • Android SDK built for x86 • emulator-5554                        • android-x86 • Android 9 (API 28) (emulator)
    • iPhone XS                 • 48A5A62A-7DB6-4C21-A7A9-961A089519D1 • ios         • iOS 12.0 (simulator)

• No issues found!

Activity

  1. added this to the milestone on Sep 24, 2018
  2. added
    engineflutter/engine related. See also e: labels.
    on Dec 10, 2018
  3. cbenhagen commented on Jan 20, 2019

    @cbenhagen
    ContributorAuthor

    @zoechi can you reevaluate the importance of this please? Either I am being very stupid here or this is a quite serious issue. Hoping for the former of course.

  4. YotaVGA commented on Jan 21, 2019

    @YotaVGA

    I am experimenting exactly the same problem. I'd at least like to know if it is on purpose or not. If it is not the wiki should be a lot more clear than this, and I don't get why a future should be returned in this case...

    This is my flutter doctor (only tested on android by now):

    [✓] Flutter (Channel stable, v1.0.0, on Linux, locale it_IT.UTF-8)
        • Flutter version 1.0.0 at /opt/flutter
        • Framework revision 5391447fae (7 settimane fa), 2018-11-29 19:41:26 -0800
        • Engine revision 7375a0f414
        • Dart version 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)
    
    [✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
        • Android SDK at /home/totoro/Android/Sdk
        • Android NDK at /home/totoro/Android/Sdk/ndk-bundle
        • Platform android-28, build-tools 28.0.3
        • ANDROID_HOME = /opt/android-sdk
        • Java binary at: /opt/android-studio/jre/bin/java
        • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
        • All Android licenses accepted.
    
    [✓] Android Studio (version 3.2)
        • Android Studio at /opt/android-studio
        • Flutter plugin version 31.3.1
        • Dart plugin version 181.5656
        • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
    
    [✓] IntelliJ IDEA Community Edition (version 2018.2)
        • IntelliJ at /usr/share/idea
        • Flutter plugin version 31.1.3
        • Dart plugin version 182.5124
    
    [✓] Connected device (1 available)
        • ASUS Z012S • G8AZCY059598BK8 • android-arm64 • Android 8.0.0 (API 26)
    
    • No issues found!
  5. zoechi commented on Jan 21, 2019

    @zoechi
    Contributor
  6. cbenhagen commented on Jan 21, 2019

    @cbenhagen
    ContributorAuthor

    @zoechi I also wish people would use the upvote feature more but while having many upvotes is a good sign that an issue is important to many users, having no upvotes does not have to mean it can not be important. People writing plugins will just work around this issue by using threads or a dispatch_queue instead upvoting an issue. Like here for example: #25959

    @YotaVGA please also give this issue your upvote.

  7. zoechi commented on Jan 21, 2019

    @zoechi
    Contributor

    @cbenhagen The comment was meant as hint that not even you upvoted and for others who stumble upon the issue later to get the idea as well.
    While upvotes are relevant they are not the only criteria, but there are currently 4000+ open issues and lots of them are important.

    That being said, plugins and infrastructure are on the roadmap for the next quarter https://github.com/flutter/flutter/wiki/Roadmap#ecosystem.

  8. mehmetf commented on Feb 18, 2019

    @mehmetf
    Contributor

    My understanding is that this is by design:

    • Plugins run on platform thread (basically UI thread of native OS)
    • If you block UI thread, you will have a bad time (even though Flutter uses its own rendering thread). I am fairly certain gesture recognition would be affected since gestures flow through native UI thread first and has to be delegated to FlutterView. I am less sure whether rendering will be affected (for instance if you had a looping animation on Flutter). Perhaps @cbenhagen can test that for us.

    @chinmaygarde as a fact checker emeritus here :-)

  9. chinmaygarde commented on Feb 19, 2019

    @chinmaygarde
    Contributor

    TL;DR: This is by design. The highlighted section from the wiki is part of a larger paragraph that provides more context and a specific recommendation against the example attached to this issue.

    That sentence on the wiki is part of a larger discussion on platform threading behavior, the entirety of which I have pasted below:

    "Even though (1)blocking the platform thread for inordinate amounts of time will not cause jank in Flutter applications(1), (2)platforms do impose restrictions on expensive operations on this thread(2). So (3)it is advised that any expensive work in response to platform messages be performed on separate worker threads(3) (unrelated to the four threads discussed above) before having the responses queued back on the the platform thread for submission to the engine. Not doing so may result in platform-specific watchdogs terminating the application(4)."

    Dissecting that paragraph,

    1. From the perspective of the Flutter engine, the platform thread is like any other thread. None of the animation or frame workloads are processed on this thread. If you were to add a spinner to a Flutter application and block the platform thread, the spinner would keep spinning (without jank).
    2. Platforms have their own restrictions on what you can do on the the main thread. Blocking this thread will prevent the system UI frameworks from delivering input events (causing your UI to freeze in response to touches) and in cases where this thread is blocked for a very long time, may even cause the system watchdog to terminate your application.
    3. Blocking the platform thread via Thread.sleep(10000); counters the recommendation made on that wiki page. If you wanted to achieve this same effect, you could setup a timer on the platform thread and signal the same after the timeout. Or, more realistically, perform the expensive operation on a background thread.
    4. As discussed, the platform is no longer sending pointer events (because it's main thread is blocked) and in extreme cases, you application may be terminated.

    Suggestions on making the section in the wiki clearer are welcome of course.

  10. svaghar commented on Mar 7, 2019

    @svaghar

    I have ran into exact same issue discussed here. The main design question here is: why the platform plugin code is run on the main ui thread? If there is a convincing answer, then this is not a bug. Otherwise the current design forces developer to create a background thread.

  11. chinmaygarde commented on Mar 7, 2019

    @chinmaygarde
    Contributor

    The design was geared with the anticipation that plugins would be written to access platform specific APIs. The majority of such APIs on both iOS and Android are not thread safe and must be accessed from their "main" threads. The design didn't want to have authors of plugins immediately rethread all their platform plugin calls to the main thread.

    For long running tasks, having the tasks run on a Flutter managed thread is also not very flexible. Would the engine have to maintain a thread or a pool or workers for parallel task execution? Besides , platforms already provide fairly ergonomic ways of dispatching tasks to worker threads (libDispatch on iOS for example).

    Since the author of the plugin had the most knowledge about the kinds of tasks their plugin calls would make, this design was deemed most flexible and least surprising (to the majority of anticipated use cases).

  12. svaghar commented on Mar 7, 2019

    @svaghar

    @chinmaygarde thanks for explaining. couple of questions.

    1. I read the The-Engine-architecture on flutter wiki which explains 4 threads. I see two threads in my logcat: One running flutter/dart code and one thread running the java plugin code. Plugin runs on the UI thread. Which thread is running the flutter code?

    2. My "long" running platfrom code are camera2 operations and filters. These could take 100s msecs to 1 second. What is the best way to design this? On a separate worker thread (e.g. Looper)?

  13. 5 remaining items

  14. p30arena commented on Apr 11, 2019

    @p30arena
    Contributor

    I want to draw your attention to this thread:

    #30911

    You should not call Result methods from a thread other than the main thread.

  15. derolf commented on Aug 30, 2019

    @derolf

    I am initializing some stuff when the PlatformView is created at startup of the app, which might take some time. During that time, the app freezes.

    Is that intended behavior?

  16. added a commit that references this issue on May 3, 2020
  17. markfili commented on May 12, 2020

    @markfili

    I opted for coroutines in onMethodCall:

    CoroutineScope(Dispatchers.Default).launch {
        val response = handleCall(call)
        launch(Dispatchers.Main) {
            result.success(response)
        }
    }
    

    this moves heavy operations to a background computation thread and then calls result on the main thread.
    added org.jetbrains.kotlinx:kotlinx-coroutines-core and kotlinx-coroutines-android to android folder's build.gradle

  18. added
    P2Important issues not at the top of the work list
    on May 29, 2020
  19. Ehesp commented on Jun 2, 2020

    @Ehesp
    Member

    This can be achieved on Android using the Task API fairly easily:

     @Override
      public void onMethodCall(MethodCall call, @NonNull final MethodChannel.Result result) {
    
    	Tasks.call(executor, () -> {
          // Do something expensive on a separate thread
          return "done";
        }).addOnCompleteListener(task -> {
         if (task.isSuccessful()) {
           result.success(task.getResult()); // "done"
         } else {
           result.error("error", task.getException())
         }
        });

    The executor can then be some thread pool (see Executors) - whatever you use depends on your performance & requirements.

  20. drriguz commented on Jul 16, 2020

    @drriguz

    I have the same issue when implementing the encryptions plugin, I tried to use FlutterIsolate to wrap calls but it's super slower than before, so I ended using native threads.

    In ios, a DispatchQueue is used:

    let dispQueue = DispatchQueue(label: "com.riguz.encryptions");
    
    // 
    dispQueue.async {
    someresult = ... // handle the heavy logic
    DispatchQueue.main.async {
        result(someresult);
    }
    }

    And in Android is pretty much the same idea, just use a new thread and Looper.getMainLooper()

    I want to know if there is some official guide about this?

  21. removed this from the milestone on Aug 17, 2020
  22. darshankawar commented on Oct 2, 2020

    @darshankawar
    Member

    Closing this as working by design and per this and this comment.

    If you think this was closed by mistake and should be kept open, please write in comments and I'll reopen.
    Thanks.

  23. github-actions commented on Aug 12, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  24. locked as resolved and limited conversation to collaborators on Aug 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work liste: embedderUsers of the Embedder APIengineflutter/engine related. See also e: labels.packageflutter/packages repository. See also p: labels.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions