【问题标题】:Thread.Sleep never returnsThread.Sleep 永不返回
【发布时间】:2015-01-10 21:32:24
【问题描述】:

我有一个在表单上绘制一些东西的应用程序(只是一个测试应用程序)。我遇到了 Thread.Sleep 的问题。我使用每 100 毫秒启动 DrawinArea 重绘的线程。它工作正常,但有时该线程无法从 This.Sleep(100) 调用返回。我检查了调试器,我确定问题出在 Thread.Sleep() 中。

注意:它需要 Gtk# 才能运行。

using System;
using Gtk;
using Cairo;
using System.Threading;
using System.Timers;

public partial class MainWindow: Gtk.Window
{

    Thread redrawThread = new Thread(Redrawer);

    //System.Timers.Timer timer = new System.Timers.Timer();

    static int calls = 0;

    public MainWindow()
        : base(Gtk.WindowType.Toplevel)
    {
        Build();
        this.drawingarea2.ExposeEvent += OnExpose;

        redrawThread.Name = "Redraw Thread";

        redrawThread.Start(this);
        /*
        timer.Interval = 100;
        timer.Elapsed += (sender, e) =>
        {
            drawingarea2.QueueDraw();
        };
        timer.AutoReset = true;
        timer.Start();*/

    }

    protected void OnDeleteEvent(object sender, DeleteEventArgs a)
    {
        redrawThread.Abort();
        Application.Quit();
        a.RetVal = true;
    }


    static void Redrawer(object wnd) {
        var area = (MainWindow)wnd;
        while (true)
        {
            Thread.Sleep(100);
            area.QueueDraw();
        }
    }

    int x = 200;
    int x_mod = 10;
    int y = 150;
    int y_mod = 10;



    void OnExpose(object sender, ExposeEventArgs args) {

        var area = (DrawingArea)sender;

        if (x + 10 >= drawingarea2.Allocation.Width || x - 10 < 0)
            x_mod = -x_mod;

        if (y + 10 >= drawingarea2.Allocation.Height || y - 10 < 0)
            y_mod = -y_mod;

        x += x_mod;
        y += y_mod;

        var ny = Math.Abs(y - drawingarea2.Allocation.Height);

        using (var ctx = Gdk.CairoHelper.Create(area.GdkWindow))
        {


            ctx.LineWidth = 9;
            ctx.SetSourceRGB(0.7, 0.2, 0.0);

            ctx.Arc(x, ny, 10, 0, 2*Math.PI);
            ctx.StrokePreserve();

            ctx.SetSourceRGB(0.3, 0.4, 0.6);
            ctx.Fill();

            ctx.GetTarget().Dispose();
        }


    }

}

一旦遇到这个问题,我决定切换到 System.Timers.Timer,但我也遇到了问题。定时器在一段时间后停止触发事件。我搜索了这个问题,发现如果没有对它的引用,GC可以破坏定时器。我的计时器是一个类成员,所以引用总是存在的,但无论如何,它都会停止。有什么问题?

【问题讨论】:

  • 我想不出任何 GTK# 可以做的事情会阻止 Thread.Sleep 调用返回。你说你确定这是睡眠调用,你是如何具体使用调试器去发现的?
  • 首先,永远不要将thread.Sleep 用于 UI 线程。正如您在问题中提到的,切换到 timer 路线并提出您的问题。
  • @L.B Thread.Sleep 在 UI 线程中不使用,它是并行使用的,使 UI 重绘。在我的情况下,用户界面总是响应式的。
  • @Scott,我为我的线程设置了一个名称,在看到该问题发生后,我暂停应用程序并查看 VS 中的线程列表,发现该线程在 Thread.Sleep 调用中。另外,我使用了一种消息来指示执行步骤,显示在 Thread.Sleep 之前插入的消息和在它之后插入的消息 - 不是。
  • 这只是表明当你点击暂停时它处于睡眠呼叫中,而不是它被卡在睡眠呼叫中。让area.QueueDraw(); 停止运行,即使你调用它也会给你同样的行为。另外,您使用了哪些“消息类型”以及您是如何阅读输出的?如果您在 QueueDraw 调用“卡”在 sleep 调用中时在其上设置断点,是否会触发断点?

标签: c# multithreading timer gtk#


【解决方案1】:

QueueDraw() 的文档中说

相当于为小部件的整个区域调用 Widget.QueueDrawArea。

阅读QueueDrawArea 的文档说明

使小部件 [...] 的矩形区域无效。一旦主循环空闲(大致处理完当前批次的事件之后),窗口将接收所有已失效区域联合的 Widget.ExposeEvent 事件。

每 100 毫秒,您就会在主窗口的队列中放置另一条消息,而主窗口处理此请求的时间超过 100 毫秒。因为您产生重绘请求的速度比它满足重绘请求的速度要快,所以这会淹没队列,一旦达到其最大大小,就无法接收到新消息并且它会“锁定”。

当您声明您通过更新窗口的标题对此进行了测试时,更新还需要进入队列才能完成,但是由于队列已满,这些标题更改从未出现。睡眠没有挂起,只是您的调试方法由于相同的潜在问题而被破坏。

解决方法是减少拨打QueueDraw()

【讨论】:

  • 我增加了间隔,它似乎工作!谢谢!我为此浪费了一整天,不知道为什么我自己还没有弄清楚。
  • @Xanx,您可能希望为此实施某种自适应节流,以避免出现大量瞬态计算机负载仍然永久锁定线程的情况。
  • 整个 Redrawer 方法对我来说有一股代码的味道,我不确定你想要完成什么,但我相信有更好的方法来做到这一点。
  • 好吧,我需要模拟一个显示其图形表示的过程。我每 100 毫秒计算一次进程状态,然后将其绘制在绘图区域上。
猜你喜欢
  • 2018-01-09
  • 2020-02-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-15
  • 2018-01-11
  • 2011-07-25
  • 1970-01-01
相关资源
最近更新 更多