Java AWT drawImage 竞争条件 - 如何使用同步来避免它

标签 java asynchronous awt synchronized doublebuffered

经过数小时的调试和分析,我终于设法找出竞争条件的原因。解决它是另一回事!

为了查看实际的竞争条件,我在调试过程中以某种方式录制了一段视频。从那以后,我进一步了解了情况,所以请原谅作为调试过程的一部分实现的糟糕评论和愚蠢机制。

http://screencast.com/t/aTAk1NOVanjR

因此,情况是:我们有一个表面(即 java.awt.Frame 或 Window)的双缓冲实现,其中有一个正在进行的线程实质上是连续循环,调用渲染过程(执行 UI 布局并渲染它到后台缓冲区),然后在渲染后,将渲染区域从后台缓冲区传输到屏幕。

这是双缓冲渲染的伪代码版本(Surface.java 的完整版本第 824 行):

public RenderedRegions render() {
    // pseudo code
    RenderedRegions r = super.render();
    if (r==null) // nothing rendered
        return
    for (region in r)
        establish max bounds
    blit(max bounds)
    return r;
}

与任何 AWT 表面实现一样,它还实现了(AWT.java 中的第 507 行 - 链接限制 :( - 使用 Surface.java 链接,将 core/Surface.java 替换为 plat/AWT。 java) paint/update 覆盖,它也从后台缓冲区 blit 到屏幕:

        public void paint(Graphics gr) {
            Rectangle r = gr.getClipBounds();
            refreshFromBackbuffer(r.x - leftInset, r.y - topInset, r.width, r.height);
        }

Blitting 是使用 drawImage() 函数实现的(AWT.java 中的第 371 行):

    /** synchronized as otherwise it is possible to blit before images have been rendered to the backbuffer */
    public synchronized void blit(PixelBuffer s, int sx, int sy, int dx, int dy, int dx2, int dy2) {
        discoverInsets();
        try {
            window.getGraphics().drawImage(((AWTPixelBuffer)s).i,
                              dx + leftInset, dy + topInset,     // destination topleft corner
                              dx2 + leftInset, dy2 + topInset,   // destination bottomright corner
                              sx, sy,                            // source topleft corner
                              sx + (dx2 - dx), sy + (dy2 - dy),  // source bottomright corner
                              null);
        } catch (NullPointerException npe) { /* FIXME: handle this gracefully */ }
    }

(警告:这是我开始做假设的地方!)

这里的问题似乎是 drawImage 是异步的,通过 paint/update 来自 refreshBackBuffer() 的 blit 首先被调用,但发生其次。

所以... blit 已经同步了。防止竞争条件的明显方法不起作用。 :(

到目前为止,我想出了两个解决方案,但都不理想:

  1. 在下一个渲染过程中重新 blit
    缺点:性能受到影响,由于遇到竞争条件(有效屏幕 -> 无效屏幕 -> 有效屏幕),仍然会出现一些闪烁

  2. 不要在绘制/更新时 blit,而是设置刷新边界并在下一个渲染过程中使用这些边界
    缺点:当屏幕无效且主应用程序线程正在追赶时出现黑色闪烁

这里 (1) 似乎是两害相权取其轻。 编辑:并且 (2) 不起作用,出现空白屏幕...(1) 工作正常,但只是掩盖了可能仍然存在的问题。

由于我对 synchronized 和如何使用它的理解薄弱,我所希望的是一种锁定机制,它以某种方式解释了 drawImage() 的异步性质。

或者也许使用 ImageObserver?

请注意,由于应用程序的性质(Vexi,对于那些感兴趣的人,网站已经过时,我只能使用 2 个超链接)渲染线程必须在绘制/更新之外 - 它有一个单线程脚本模型和布局过程(渲染的子过程)调用脚本。

最佳答案

更新:这里的好方法:AWT custom rendering - capture smooth resizes and eliminate resize flicker


这里的答案是从 paint() 线程中删除所有 blitting,即只从程序线程中的后台缓冲区刷新。这与 Jochen Bedersdorfer 建议的答案相反,但他的答案永远不会为我们工作,因为该程序有自己的脚本模型,该模型与驱动渲染的布局模型集成,因此这一切都必须按顺序发生。

(推测)一些问题源于 Java 对加速图形芯片组的多显示器支持不尽如人意,正如我遇到的 this problem适配使用BufferStrategy时,是direct3d+Java的矛盾。

本质上,paint()update() 被简化为阻塞调用。这样效果好很多,但有一个缺点 - 无法平滑调整大小。

private class InnerFrame extends Frame() {
    public void update(Graphics g) { }
    public void paint(Graphics g) { }
    ....
}

尽管我对这种方法不是 100% 满意,但我最终还是使用了缓冲策略,因为在我看来,渲染到图像、然后将完整图像复制到 BufferStrategy,然后执行 show() 到屏幕。

我还实现了一个基于 Swing 的替代方案,但我还是不太喜欢那样。它使用带有 ImageIcon 的 JLabel,由此程序线程(而不是 EDT)绘制到由 ImageIcon 包装的图像。

我确定当我有更多时间更有目的地研究这个问题时,我肯定会有一个后续问题要问,但现在我有两个工作实现或多或少地解决了这里发布的最初问题 - 和通过发现它们,我学到了很多东西。

关于Java AWT drawImage 竞争条件 - 如何使用同步来避免它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4952302/

相关文章:

javascript - 为什么这个使用 await/act/async 的官方 React 测试配方真的有效?

asynchronous - 什么时候onDone事件可用于Dart中的StreamSubscription?

java - 你能帮助指导我让这个 Swing 应用程序正确加载图像吗?

Java:将 java.awt.Color 转换为 javafx.scene.paint.Color

java - KeyListener 的调用顺序是否有保证?

java - 根据部署的 jar 动态创建导航菜单

java - SimpleJdbcCall异常: Required input parameter is missing

java - SWT:包含跨列单元格的表格

java - android 中的 BasicAuthentication for webview 不起作用

c# - Monotouch 和异步 CTP