首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何避免忙碌的纺纱一个可暂停的制片人?

如何避免忙碌的纺纱一个可暂停的制片人?
EN

Stack Overflow用户
提问于 2018-09-09 14:41:58
回答 1查看 548关注 0票数 0

背景

我发现了一些GIF animation library,它有一个后台线程不断地将当前帧解码为位图,它是其他线程的生成器:

代码语言:javascript
复制
@Volatile
private var mIsPlaying: Boolean = false

...
while (mIsRunning) {
    if (mIsPlaying) {
        val delay = mGifDecoder.decodeNextFrame()
        Thread.sleep(delay.toLong())
        i = (i + 1) % frameCount
        listener.onGotFrame(bitmap, i, frameCount)
    }
}

我为此制作的POC示例是可用的here

问题所在

这是低效的,因为当线程到达mIsPlaying为false的点时,它只是在那里等待并不断地检查它。事实上,它会导致这个线程以某种方式做更多的CPU使用(我通过分析器检查过)。

事实上,它从占CPU的3-5%增加到了12%-14%。

我试过的

过去我对线程有很好的了解,我知道简单地放置一个waitnotify是危险的,因为它仍然会导致线程等待一些罕见的情况。例如,当它确定它应该等待,然后在它开始等待之前,外部线程标记它不应该等待。

这种行为被称为“忙碌旋转”或“忙碌等待”,实际上对于需要协同工作的多个线程,here有一些解决方案。

但在这里我觉得有点不一样。等待不是等待某个线程完成它的工作。这是暂时的等待。

这里的另一个问题是使用者线程是UI线程,因为它需要获取位图并查看它,所以它不能像消费者生产者解决方案那样等待工作(用户界面绝不能等待,因为它会导致"jank")。

问题是

避免在这里旋转的正确方法是什么?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-09-10 05:17:58

所以我决定使用等待通知机制,因为我找不到任何好的类来处理这个案例。这需要精细的思考,因为以错误的方式使用线程可能导致(在非常罕见的情况下)无限等待和其他奇怪的事情。

即使在UI线程上,我也决定使用synchronized,但我使用它的同时也保证它不会出现很长时间。这是因为UI线程一般不应该等待其他线程。为此,我可以使用一个线程池(大小为1),以避免UI线程等待同步部分,但我认为它足够好。

下面是我为gifPlayer修改的代码:

代码语言:javascript
复制
class GifPlayer(private val listener: GifListener) : Runnable {
    private var playThread: Thread? = null
    private val gifDecoder: GifDecoder = GifDecoder()
    private var sourceType: SourceType? = null
    private var filePath: String? = null
    private var sourceBuffer: ByteArray? = null
    private var isPlaying = AtomicBoolean(false)

    interface GifListener {
        fun onGotFrame(bitmap: Bitmap, frame: Int, frameCount: Int)

        fun onError()
    }

    @UiThread
    fun setFilePath(filePath: String) {
        sourceType = SourceType.SOURCE_PATH
        this.filePath = filePath
    }

    @UiThread
    fun setBuffer(buffer: ByteArray) {
        sourceType = SourceType.SOURCE_BUFFER
        sourceBuffer = buffer
    }

    @UiThread
    fun start() {
        if (sourceType != null) {
            playThread = Thread(this)
            synchronized(this) {
                isPlaying.set(true)
            }
            playThread!!.start()
        }
    }

    @UiThread
    fun stop() {
        playThread?.interrupt()
    }

    @UiThread
    fun pause() {
        synchronized(this) {
            isPlaying.set(false)
            (this as java.lang.Object).notify()
        }
    }

    @UiThread
    fun resume() {
        synchronized(this) {
            isPlaying.set(true)
            (this as java.lang.Object).notify()
        }
    }

    @UiThread
    fun toggle() {
        synchronized(this) {
            isPlaying.set(!isPlaying.get())
            (this as java.lang.Object).notify()
        }
    }

    override fun run() {
        try {
            val isLoadOk: Boolean = if (sourceType == SourceType.SOURCE_PATH) {
                gifDecoder.load(filePath)
            } else {
                gifDecoder.load(sourceBuffer)
            }
            val bitmap = gifDecoder.bitmap
            if (!isLoadOk || bitmap == null) {
                listener.onError()
                gifDecoder.recycle()
                return
            }
            var i = -1
            val frameCount = gifDecoder.frameCount
            gifDecoder.setCurIndex(i)
            while (true) {
                if (isPlaying.get()) {
                    val delay = gifDecoder.decodeNextFrame()
                    Thread.sleep(delay.toLong())
                    i = (i + 1) % frameCount
                    listener.onGotFrame(bitmap, i, frameCount)
                } else {
                    synchronized(this@GifPlayer) {
                        if (!isPlaying.get())
                            (this@GifPlayer as java.lang.Object).wait()
                    }
                }
            }
        } catch (interrupted: InterruptedException) {
        } catch (e: Exception) {
            e.printStackTrace()
            listener.onError()
        } finally {
        }
    }


    internal enum class SourceType {
        SOURCE_PATH, SOURCE_BUFFER
    }

}

在做了一些工作之后,我找到了一种使用HandlerThread的很好的方法。我认为它更好,并且可能有更好的稳定性。下面是代码:

代码语言:javascript
复制
open class GifPlayer(private val listener: GifListener) {
        private val uiHandler = Handler(Looper.getMainLooper())
        private var playerHandlerThread: HandlerThread? = null
        private var playerHandler: Handler? = null
        private val gifDecoder: GifDecoder = GifDecoder()
        private var currentFrame: Int = -1
        var state: State = State.IDLE
            private set
        private val playRunnable: Runnable

        enum class State {
            IDLE, PAUSED, PLAYING, RECYCLED, ERROR
        }

        interface GifListener {
            fun onGotFrame(bitmap: Bitmap, frame: Int, frameCount: Int)

            fun onError()
        }

        init {
            playRunnable = object : Runnable {
                override fun run() {
                    val frameCount = gifDecoder.frameCount
                    gifDecoder.setCurIndex(currentFrame)
                    currentFrame = (currentFrame + 1) % frameCount
                    val bitmap = gifDecoder.bitmap
                    val delay = gifDecoder.decodeNextFrame().toLong()
                    uiHandler.post {
                        listener.onGotFrame(bitmap, currentFrame, frameCount)
                        if (state == State.PLAYING)
                            playerHandler!!.postDelayed(this, delay)
                    }
                }
            }
        }

        @Suppress("unused")
        protected fun finalize() {
            stop()
        }

        @UiThread
        fun start(filePath: String): Boolean {
            if (state != State.IDLE)
                return false
            currentFrame = -1
            state = State.PLAYING
            playerHandlerThread = HandlerThread("GifPlayer")
            playerHandlerThread!!.start()
            playerHandler = Handler(playerHandlerThread!!.looper)
            playerHandler!!.post {
                gifDecoder.load(filePath)
                val bitmap = gifDecoder.bitmap
                if (bitmap != null) {
                    playRunnable.run()
                } else {
                    gifDecoder.recycle()
                    uiHandler.post {
                        state = State.ERROR
                        listener.onError()
                    }
                    return@post
                }
            }
            return true
        }

        @UiThread
        fun stop(): Boolean {
            if (state == State.IDLE)
                return false
            state = State.IDLE
            playerHandler!!.removeCallbacks(playRunnable)
            playerHandlerThread!!.quit()
            playerHandlerThread = null
            playerHandler = null
            return true
        }

        @UiThread
        fun pause(): Boolean {
            if (state != State.PLAYING)
                return false
            state = State.PAUSED
            playerHandler?.removeCallbacks(playRunnable)
            return true
        }

        @UiThread
        fun resume(): Boolean {
            if (state != State.PAUSED)
                return false
            state = State.PLAYING
            playerHandler?.removeCallbacks(playRunnable)
            playRunnable.run()
            return true
        }

        @UiThread
        fun toggle(): Boolean {
            when (state) {
                State.PLAYING -> pause()
                State.PAUSED -> resume()
                else -> return false
            }
            return true
        }

    }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52245590

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档