首先本人是个编程菜鸟
我想实现效果:一排图片从左上角往右下角移动,周而复始。
试了一下,在FORM中创建16个picturebox然后循环移动所有的picturebox很卡。所以想用多线程实现,每个线程创建一个picturebox,然后控制它往右下角走,每50ms走一步,但是我写的程序有问题,窗口中显示不出创建的pictruebox
请大家指点一下,拜谢!    public partial class Form1 : Form
    {
        Point fa = new Point(-120, -80);
        public Form1()
        {
            InitializeComponent();
        }        private void Form1_Load(object sender, EventArgs e)
        {
            Thread thr = new Thread(showpic);
            thr.Start();
        }        void showpic()
        {
            PictureBox picb = new PictureBox();
            picb.ImageLocation = "picture\\IMG_20181014_100220.jpg";
            picb.Location = fa;
            picb.Size = new Size(120, 80);
            picb.SizeMode = PictureBoxSizeMode.Zoom;
            while(true)
            {
                picb.Location = new Point(picb.Location.X + 16, picb.Location.Y - 9);
                if (picb.Location.X > 1920)
                    picb.Location = fa;
                Thread.Sleep(50);
            }
        }
    }

解决方案 »

  1.   

     当然不会显示啦
    源码 动态创建的 picturebox 控件都没有装到 Form1 中, 显个毛 ,不过,按你的源码来看,即使显示,也是卡的, 
    不是这样的 显示分步显示图片的
      

  2.   

    多线程不会……在线程中加this.controls.add(picb)会报线程间操作无效,应该怎么搞?
      

  3.   

    多线程改变控件状态,在逻辑上是不靠谱的,不能操作控件。操作控件必须使用 BeginInvoke/Invoke 委托注册方式来排队操作。
      

  4.   


            Point fa = new Point(0, 0);
            private void Form1_Load(object sender, EventArgs e)
            {
                Task.Run(() => { showpic(); });        }
            void showpic()
            {
                PictureBox picb = new PictureBox();
                picb.ImageLocation = "Image\\1.jpg";
                picb.Location = fa;
                picb.Size = new Size(120, 80);
                picb.SizeMode = PictureBoxSizeMode.Zoom;
                Invoke(new EventHandler(delegate { Controls.Add(picb); }));
                while (true)
                {
                    Invoke(new EventHandler(delegate
                    {
                        picb.Location = new Point(picb.Location.X + 16, picb.Location.Y + 9);
                        if (picb.Location.X > 1920)
                            picb.Location = fa;
                    }));
                    Thread.Sleep(50);
                }
            }
      

  5.   

    最大的毛病(甚至可以说唯一的毛病)就在于“死循环+Sleep”这种编程概念,坑死人。就好像是去炒股的人只知道追涨杀跌,完全没有搞懂投资规则。
      

  6.   

    1.创建控件无需线程执行,可在InitializeComponent()内添加。 并且需添加到控件集合中:
    Controls.Add(picb);2.while循环内嵌Sleep是不对的,你可以使用timer:Timer timer = new Timer
                {
                    Interval = 50,
                    Enabled = true
                };
                timer.Tick += timer1_Tick;
            private void timer1_Tick(object sender, System.EventArgs e)
            {
     picb.Location = new Point(picb.Location.X + 16, picb.Location.Y - 9);
                    if (picb.Location.X > 1920)
                        picb.Location = fa;
            }
    3.Y坐标一直在减,一直在form外 怎么显示?
      

  7.   

    过去,经常使用 Timer 来达到“按帧控制”的目地。现在可以使用任务机制的异步 Task.Delay 功能:private async void Form1_Load(object sender, EventArgs e)
    {
        var label = new Label();
        this.Controls.Add(label);
        var rnd = new Random();
        var x = (int)(rnd.NextDouble() * 50) - 25;
        var y = (int)(rnd.NextDouble() * 50) - 25;
        while (true)
        {
            label.Text = DateTime.Now.ToString("mm:ss");
            label.Top += y;
            label.Left += x;
            if (label.Left >= 0 && label.Left <= this.ClientSize.Width && label.Top >= 0 && label.Top <= this.ClientSize.Height)
                await Task.Delay(500);
            else
            {
                label.Top -= y;
                label.Left -= x;
                x = (int)(rnd.NextDouble() * 50) - 25;
                y = (int)(rnd.NextDouble() * 50) - 25;
            }
        }
    }
    这里跟什么线程没有直接关系。这里最主要地是,在 Task.Delay 语句执行时,方法 Form_Load 就已经结束了;然后500毫秒之后执行到  label.Text = DateTim....  语句时,是异步调用的。这其实是基于“事件发生时才回调委托”的概念,要知道交互操作是异步编程的,而不是阻塞的概念。
      

  8.   

    使用 c# 7.0 的语法,程序更加清晰private async void Form1_Load(object sender, EventArgs e)
    {
        var label = new Label();
        this.Controls.Add(label);
        void 设置方向(out int a, out int b)
        {
            var rnd = new Random();
            a = (int)(rnd.NextDouble() * 50) - 25;
            b = (int)(rnd.NextDouble() * 50) - 25;
        }
        设置方向(out int x, out int y);
        while (true)
        {
            label.Text = DateTime.Now.ToString("mm:ss");
            label.Top += y;
            label.Left += x;
            if (label.Left >= 0 && label.Left <= this.ClientSize.Width && label.Top >= 0 && label.Top <= this.ClientSize.Height)
                await Task.Delay(500);
            else
            {
                label.Top -= y;
                label.Left -= x;
                设置方向(out x, out y);
            }
        }
    }
    但是语法是小伎俩,关键是基本的理念。一个语法学起来可能需要1分钟,而一个理念需要你学习5年。
      

  9.   


    这样说说似乎就有点偏激了,很多程序本身就是一个死循环。
    驱动一个事件要么是死循环,要么刷Timer。比如你让一个设备重复一个抓料放料动作,它本身就是一个死循环。 
    你判断一个动作是否到位,要么用死循环+Sleep(必须+Sleep),要么刷Timer. 
      

  10.   

    Task.Delay() 和 Thread.Sleep() 区别
    1、Thread.Sleep 是同步延迟,Task.Delay异步延迟。2、Thread.Sleep 会阻塞线程,Task.Delay不会。3、Thread.Sleep不能取消,Task.Delay可以。4. Task.Delay() 比 Thread.Sleep() 消耗更多的资源,但是Task.Delay()可用于为方法返回Task类型;或者根据CancellationToken取消标记动态取消等待5. Task.Delay() 实质创建一个运行给定时间的任务, Thread.Sleep() 使当前线程休眠给定时间。
      

  11.   


    这说明你完全不理解事件。交互程序运行中自然是几十万、无数的事件触发,生生不息地不断触发的。但是僵化的说法是看不到运动前进,只能看到诡异地反复。例如上面的 async void Form_Load 过程例子,当执行到 Delay(500) 的时候,立刻,这个过程就结束了。事件只是注册一个回调而已。如果连注册回调跟阻塞调用都分不清楚,自然就会混淆编程概念。
      

  12.   

    c# 支持 async/await 语法,这虽然是使用了同步顺序的语法,但是要求程序员必须有最起码的异步/事件驱动编程概念。如果我们没有基本知识,那么这个语法反而就是混淆视听了,那还不如明确地用各种复杂、繁琐的事件回调语法。只有当程序员有了起码的异步交互知识,才能用好 async/await 来简化异步程序代码。没有什么代码天生是同步的、或者异步的。当你面对的交互操作不断深入、更加自然地插入了其它消息机制时,那你才会从同步顺序操作转入异步回调操作的设计。所以这就好像是一个小孩儿以为所有的蛋糕都是属于自己的,他就不知道将来有一天需要的时候只能自己买蛋糕吃而不会有别人免费送给他蛋糕。我实际上在昨天另外一个帖中,说明了,不仅仅是初学者写一个简单的 for 循环然后写什么 Refresh 代码,然后大多数人还知道了删除Refresh 但是滥用一个子线程来模拟异步事件编程机制,最后才是到了学会了异步/事件编程阶段。而 await Task.Delay 虽然可以非常方便地取代 Timer 代码,它的背后还是需要理解异步编程基本概念才能写出来这一行代码。
      

  13.   

    平心而论。你 #10 是一个   while (true) +  await Task.Delay(500) 的死循环
    和楼主的  while (true) + Sleep(500) 究竟有多大区别呢?
    我想你是否应该科普一下呢?
    这说明你完全不理解事件。交互程序运行中自然是几十万、无数的事件触发,生生不息地不断触发的。但是僵化的说法是看不到运动前进,只能看到诡异地反复。例如上面的 async void Form_Load 过程例子,当执行到 Delay(500) 的时候,立刻,这个过程就结束了。事件只是注册一个回调而已。如果连注册回调跟阻塞调用都分不清楚,自然就会混淆编程概念。
      

  14.   

    简单的“线程”概念正在被淘汰。在现在的高性能的多线程异步编程风格的程序中,会成百上千倍地多多地用 Task,多多用 await 代码。可以这么说,如果淘汰掉编程语言中几乎所有的、过去30年的同步阻塞和进程内信号量代码,那么程序员的水平就有可能提高一大步,多线程程序性能也会普遍提高。在过去,我们经常纠结说“进程中有几十、上百个线程会不会影响程序效率?”的问题。而今天,实际上 Task 机制以及 await 语法是用来应对进程中可能理论上同时并发几万个、几十万个 Task 的微过程编程控制风格。Task 是一种优化了的、高质量的、更轻更密集的管理协程,代表着未来的技术。
      

  15.   

    一定要搞清楚,await 并不是阻塞代码,这是最基本的概念。也可以自己写代码测试一下,以测试来准,胜过轻信理论。
      

  16.   

    很遗憾,async/await 代码有可能会引起误解,以为这是阻塞代码。但是这其实就是简单直接的核心技术。await 是异步回调,而并不阻塞。正因为这个语法糖,所以才能保证用很简洁、几乎跟很原始的代码相同的语法风格,而写出了事件回调才能驱动的异步程序。
      

  17.   

    许多需求设计,我们一下子就能看出简单地函数式(输入--输出)思维方式不行,需要在操作流程中需要插入更多地对外通讯交流环节。也就是说同步顺序的思维方式要变为异步回调方式。在现在的程序中,我们可能写代码var result = await Customer.去用信用卡支付(....);
    处理发货;这样的代码。包括我们简单地去调用一个多线程 I/O 方法去网上搜索点网页时,使用异步操作,也是如此。这类代码大量被使用、被设计出来。在过去,这样的回调委托代码,我们可能只能明确地用方法的回调机制、事件注册机制来实现。现在可以用 async/await 语法。但是首先需要理解其机制才能写出来明白的 async/await 代码。假设说让你画一个程序流程图,你会用流程图符号来画出同步顺序操作、跟异步回调操作(上面的 await 语法)的本质的区别吗?如果你不能在流程图上区分出来,那么肯定在语言和设计上无法区分出来同步阻塞跟异步回调的区别,那么你面对上面的那种操作设计需求,可能就无法异步,甚至可能连使用子线程去处理信用卡都不能理解。其实代码不是最重要的,如果先来画个流程图、用语言描述一下异步操作都弄不好,这才是问题。
      

  18.   

    现在的程序,将可能把进程内的并发数据的某些流程看成是几十万个并发的微过程,强调需要大量使用异步编程机制。类似代码var result = await Customer.去用信用卡支付(....);
    处理发货;
    这样的程序,当执行到 await 的时候,这个过程就结束了,当前线程已经离开这个过程而去执行过程之后的其它过程去了!然后也许200毫秒之后,也许8秒钟之后......总之是不确定的时间之后,系统才会再来回调执行等号赋值以及之后的代码,而那个时候也许是使用相同线程、也许是其它线程来回调。异步操作不一定就是使用子线程来执行的。但是更重要地是,这里没有什么“阻塞”,这个 await 动做根本不占用任何线程,所以 await 是大量协程管理的基础技术,async/await 它可以避免滥用线程。
      

  19.   

    如果你画系统流程图,特别是当你在白板上画图给同事讲解系统设计的时候,你要注意到,假设上面的这2行代码在一个叫做 abc 的过程中,那么你不能把流程图画成是先执行完毕 abc 得到发货结果然后执行 abc 后边的过程代码的流程。否则就会被人笑话了。你必须理解这个 async/await 机制,把 这里的回调时才去处理 result 和发货的独立并发时序清楚地跟顺序同步区分开来。
      

  20.   

    以理论化的说法,可以说上面的 abc 函数得到的是一个刚注册好的 Task 对象,而并不是执行了信用卡刷卡和发货的结果。然后在调用 abc 的宿主代码后边,有可能编排多种任务的流程(例如多个 Task 完成汇合之后再继续,或者是一个 Task 完成了之后就触发下一个任务.......)但是这个 Task 说法太技术化了。在理解其它人写的异步代码方面,我们至少注意到异步回调的第一个目地就是为了避免同步阻塞,起码第二个目地是避免滥用 Thread,并且我们要知道现在已经开始了一个大量(经常是并发上万个任务)使用 Task 的时代,这样可以开始来设计一个高性能的程序。
      

  21.   

    一般传统习惯上UI在主线程,事务可以放在别的线程。事务通过消息泵出结果。多个线程创建UI对象。不过楼主是要实现16个图像的顺畅拖动。建议使用定时器,每次移动一格。
      

  22.   

    我需要的是怎么提高效率,在主form中用定时器移动很卡,所以才想用多线程来做的
      

  23.   

    其实你的也无什么错,只是少了句 Invoke(new Action(()=> Controls.Add(picb)));
      

  24.   

    多线程改变控件状态,在逻辑上是不靠谱的,不能操作控件。操作控件必须使用 BeginInvoke/Invoke 委托注册方式来排队操作。
      

  25.   

    这是一个“演进的”知识学习过程。首先,作为初学者,一开始只学过简单地在主线程操作 UI 的指令。一旦过早地“毕业”了(结束培训了),根本没有学过什么大一点的 UI 交互程序设计,那么写代码的时候可能就以为写while(true)
    {
        abc.Localtion = 新的坐标位置;
        Sleep(100);
    }这类死循环代码。接下来,知道这是个坑,就开始觉得线程是个好东西了,满脑子只有线程概念了。其实早期的 VB 等等工具,以及大量的游戏软件,都是单线程的,都在 UI 主线程就能编写复杂的、世界流行的各种游戏。学过 UI 设计知识的编程者知道用 Timer 来控制游戏流程的每一帧的动做,这就是交互式程序基本概念,不需要纠结子线程。有了这类设计概念,懂得了交互世界的基本道理。而什么“多线程”此时并不重要。
      

  26.   


    这说明你完全不理解事件。交互程序运行中自然是几十万、无数的事件触发,生生不息地不断触发的。但是僵化的说法是看不到运动前进,只能看到诡异地反复。例如上面的 async void Form_Load 过程例子,当执行到 Delay(500) 的时候,立刻,这个过程就结束了。事件只是注册一个回调而已。如果连注册回调跟阻塞调用都分不清楚,自然就会混淆编程概念。说那些无用的理论干什么? 就来实现一件事,简单的小事:一台设备,有个“启动”按钮(硬件),要求按下“启动”按钮,设备运行,该怎么做?你别谈理论,就实际解决问题,请问有什么高明的办法? (我的方法要么循环+sleep,要么刷Timer)
      

  27.   


    这说明你完全不理解事件。交互程序运行中自然是几十万、无数的事件触发,生生不息地不断触发的。但是僵化的说法是看不到运动前进,只能看到诡异地反复。例如上面的 async void Form_Load 过程例子,当执行到 Delay(500) 的时候,立刻,这个过程就结束了。事件只是注册一个回调而已。如果连注册回调跟阻塞调用都分不清楚,自然就会混淆编程概念。说那些无用的理论干什么? 就来实现一件事,简单的小事:一台设备,有个“启动”按钮(硬件),要求按下“启动”按钮,设备运行,该怎么做?你别谈理论,就实际解决问题,请问有什么高明的办法? (我的方法要么循环+sleep,要么刷Timer)
    sp已经跟你说了很多次他的解决方案了:
    你按照你自己需求设计就会发现你的需求是这样的:
    a 启动按钮点击触发一个 await的硬件访问事件,这个事件结束后触发结束后的事件(正常如何,异常如何)。
    b 因为你使用了await,硬件事件启动后界面不会锁死,当硬件事件结束后根据运行结果自动触发对应的绑定事件继续处理你的业务。
    c 你只需要在硬件事件结束后成功再次执行自己的扫描任务就可以实现循环访问硬件的功能。
    d 做这些的时候注意做一个强制停止和退出的逻辑在里面这样就可以在关闭的时候关闭硬件访问等防止硬件长时间占用了。
    你有认真读他反复给你说的东西吗?没有,你只是想看到实际解决方案,但是有时候有些东西的解决方案就是要冗长的语言描述的,并不是几行代码。
      

  28.   

    sp1234讲了很多,但是因为讲的比较零碎,而且语言组织上,确实会让初学者存在困扰。
    简单组织一下:
    假设你的程序只有10个线程,
    如果代码是用下面这种写法,那么你的程序同一时间,只能对最多10个用户提供服务,因为线程是同步的,进来10个用户后,10个线程被被他们长期持有了,且无法退出:while(true){ 
        Thread.Sleep(1000);
        xxx业务代码;
    }但是如果你的程序是下面这种写法,你的程序就可以对超过10个用户提供服务,因为在await的时候,你的线程已经被暂时释放了,可以给其它用户提供服务,等时间到了,再重新分配一个线程,回来处理你的后续业务代码:while(true){ 
        await Task.Delay(1000);
        xxx业务代码;
    }