【问题标题】:While used Thread.Sleep with pictureBox, Winform freezes将 Thread.Sleep 与 pictureBox 一起使用时,Winform 冻结
【发布时间】:2013-09-14 09:32:47
【问题描述】:

我正在为一个项目工作,在该项目中,pictureBox 用于一一显示目录中的图像。

首先我点击开始按钮。它调用一个 backgroundWorker 持续运行,直到我按下停止按钮。然后在 backgroundWorker 中,我调用了一个时间间隔为 500 毫秒的计时器。它调用一个事件处理方法。

在 evenhandler 方法中,我首先从目录中获取文件名,该文件名是数字并显示在名为“PicBox”的图片框中,然后递增,然后显示下一张图像。我使用了一个 while 循环来运行整个过程,直到我单击停止按钮。

问题是它在 500 毫秒(定时器间隔)后开始并以高速显示所有图片。我想以流的形式展示它们,这样用户就不会意识到这一切都是几个图像的组合,但不是以这种速度。

我使用了 Thread.Sleep(),但它冻结了 winform,我什至无法单击停止按钮。

PictureBox代码在这里:

if (_performReceiving)
{
    try
    {
        while (_performReceiving)
        {
            switch (firstNum)
            {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                    PicBox.Image = Image.FromFile(path + "0" + firstNum + ".png");
                    this.PicBox.SizeMode = PictureBoxSizeMode.Zoom;
                    PicBox.Refresh();
                    firstNum++;
                    break;
                default:
                    PicBox.Image = Image.FromFile(path + firstNum + ".png");
                    this.PicBox.SizeMode = PictureBoxSizeMode.Zoom;
                    PicBox.Refresh();
                    firstNum++;
                    break;
            } 
        }
    }
    catch (FileNotFoundException)
    {
        MoveTimer.Stop();
    }
}

有什么建议吗?

编辑:(代码的可视化视图)

Form class
{
start_button clicked()
{
   //call backgroundWorker1
}

backgroundWorker1_DoWork()
{
   //create Timer1_handler
   //Timer1.Start()
}

Timer1_handler()
{
   //Process to get firstNames numeric value which passes to switch parameter

   :: This code posted on the main question ::
}
}

【问题讨论】:

  • 不要阻塞gui线程?
  • @iabbott 这仅适用于 c/c++。用c#试试,会报Control cannot fall through from one case label ('case 1:') to another编译错误。
  • 嘿,有没有机会因为您在BackgroundWorker 中一遍又一遍地启动计时器,它会一遍又一遍地尝试加载图像? - 另外,为什么要启动计时器,如果您想在线程中的特定点更改图像,然后委托 GUI 加载新图像,然后将其硬编码到线程中。
  • @NewBiL 与其让计时器的滴答声使用While 来添加所有图像,不如每个滴答声事件只做一个,然后调整计时器的间隔,以便以您想要的速度添加图像。计时器的间隔实际上变成了您通常睡觉的时间。
  • @NewBiL 我不会浏览整个代码库的大量转储,其中大部分无关。如果您可以将您的问题简化为一个可以演示您的问题并且不包含无关代码的简短但有效的示例,那么值得一看。

标签: c# multithreading winforms image picturebox


【解决方案1】:

没有。没有。没有。没有。

这都是错误的。 Threading 和 WinForm,你必须了解这些东西。 WinForm 在所谓的 UI 线程上运行。这是您必须在其上创建控件的线程,并且是唯一可以访问控件的线程(否则您可能会遇到死锁......或者这些天的例外情况)。

其次,后台worker在doWork中使用了后台线程。这不是 UI 线程,可以用来做很多可爱的处理。当您使用后台工作人员进行更新时(我忘记了确切的 API),它会自动为您编组到 UI 线程。我们好吗?

好的。现在这是您的问题:System.Windows.Forms.Timer 在 UI THREAD 上运行。这是一个简单的系统,它以特定的时间间隔在 UI 线程的队列中插入项目。

因此,在您的示例中,您似乎正在调用一个后台线程,然后该线程试图将其工作基于驻留在 UI 线程中的计时器。这太疯狂了。

改用 System.Threading.Timer 或完全丢弃 BackgroundWorker,只使用一个线程(UI 线程)执行此操作。由于您一次只加载 1 张图片,只要图片是本地图片,您就可以侥幸成功,而不会让 UI 过于迟钝。

Form1_Load(object sender, EventArgs e) {
    Timer t = new System.Windows.Forms.Timer();
    t.Interval = 5000;
    t.Tick += SomeEventHandlerThatLoadsAndDisplaysTheNextImage;
    t.Start();
}

或者只是正确使用 BackgroundWorker。如果你这样做,你可以添加 .sleeps。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        // this is the UI thread
        public Form1()
        {
            InitializeComponent();
            Load += new EventHandler(Form1_Load);
        }

        private BackgroundWorker worker;

        // this is the UI thread
        void Form1_Load(object sender, EventArgs e)
        {
            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            worker.RunWorkerAsync();
        }
        // this is the UI thread, the BackgroundWorker did the marshalling for you (Control.Invoke or Control.BeginInvoke)    
        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            pictureBox1.Image = (Image) e.UserState;
        }
        // this is a background thread, don't touch the controls on this thread we use .ReportProgress (offered by the backgroundWorker) to marshal back to the UI thread.
        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (iNeedToKeepRotatingImages)
            {
                Thread.Sleep(5000);
                var image = LoadAnImage(myState);
                worker.ReportProgress(0, image);
            }
        }
    }
    }

【讨论】:

  • 我有一些问题。首先,我有一个开始按钮。所以Load 将在 button_start 点击里面。是吗?你能解释一下pictureBox1.Image = (Image) e.UserState;var image = LoadAnImage(myState);这行吗?我将在哪里提供我的图像处理代码?
  • 没有。这些是供读者追求的练习。我觉得我已经提供了足够的帮助。
  • 对不起我的愚蠢问题。我是 C# 的新手,这就是为什么很多事情让我有点困惑。不管怎样,希望这个对我有用。
  • 好吧,只是那些:D。 UserState 是一个可以放置信息以便在线程之间轻松共享的地方。在这里,我将图像放入 doWork 中。你查询的那一行是我把那张图片取出来的地方。 LoadAnImage 是您加载图像的位置。 “myState”变量就是您在程序中用来跟踪当前正在查看的图像的任何内容。 HTH!
猜你喜欢
  • 2022-11-14
  • 2011-06-14
  • 1970-01-01
  • 2014-07-30
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
  • 2020-10-11
相关资源
最近更新 更多