C # WinForms формирует таймер, не тикающий внутри цикла - PullRequest
0 голосов
/ 09 мая 2019

Я создал проект на C #, приложение WinForms с фоновым классом. Класс приложения WinForms содержит одну точку и рисует ее каждый раз, когда вы вызываете метод «Refresh ()». Нажав на пробел, вы запустите фоновый класс. Фоновый класс предоставляет метод «run ()», который этот метод вызывает метод вычисления определенное количество раз (в то время как цикл). Затем метод вычисления выполняет некоторые вычисления и добавляет результаты в стек, а затем запускает таймер. Этот таймер содержит метод рисования, который берет данные из стека и сообщает Форме печатать кружок для каждой записи в стеке. (Это довольно запутанно, чтобы описать код ниже и должно быть легко понять)

Теперь у меня возникла довольно странная проблема, которую я не понимаю: (см. Пример кода ниже) В фоновом классе, когда цикл while вызывает вычисление, вычисление будет выполнено, но время не будет выполнять метод draw. Но когда вы закомментируете начало и конец цикла while и вместо этого нажимаете пробел вручную несколько раз, все работает нормально. Почему код работает без цикла и нажимает несколько раз вручную, но не с циклом while? И как я могу это исправить, чтобы заставить его работать с циклом?

Действия по воспроизведению проблемы: Создайте новое приложение WinForms в Visual Studio, замените Form1.cs приведенным ниже кодом и создайте класс BackgroundRunner.cs с приведенным ниже кодом. Затем просто запустите проект и нажмите пробел, чтобы запустить фоновый класс. Чтобы получить работающий код: Просто закомментируйте начало и конец цикла while и несколько раз нажмите пробел вручную. (движение по кругу)

Дополнительно: Да, в этом разбитом примере выглядит странно создавать два отдельных класса и таймер форм, но во всем проекте мне нужно, чтобы он работал таким образом.

Заранее спасибо.

Я уже погуглил и нашел решения, такие как таймер и пользовательский интерфейс, которые должны быть в одном потоке и т. Д. Но я думаю, что оба находятся в одном потоке, потому что он работает без цикла.

Это класс Form1.cs:

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

namespace TestApp
{
    public partial class Form1 : Form
    {

        private Point p;
        private BackgroundRunner runner;
        private int count = 0;
        private int max = 10;

        public Form1() {
            InitializeComponent();
            p = new Point(0, 0);
            runner = new BackgroundRunner(this);
        }

        private void Form1_Load(object sender, EventArgs e) {

        }

        //Method to refresh the form (draws the single point)
        override
        protected void OnPaint(PaintEventArgs e) {
            drawPoint(p, e.Graphics);
        }

        //Method to draw a point
        private void drawPoint(Point p, Graphics g) {
            Brush b = new SolidBrush(Color.Black);
            g.FillEllipse(b, p.X, p.Y, 10, 10);
        }

        //when you press the space bar the runner will start
        override
        protected void OnKeyDown(KeyEventArgs e) {
            if(e.KeyValue == 32) {
                runner.run();
            }
        }

        public int getCount() {
            return count;
        }

        public int getMax() {
            return max;
        }

        //sets point, refreshes the form and increases the count
        public void setPoint(Point pnew) {
            p = pnew;
            Refresh();
            count++;
        }
    }
}

А это класс BackgroundRunner.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestApp
{
    class BackgroundRunner
    {
        private Form1 form;
        private System.Windows.Forms.Timer timer;
        private Stack<int> test;
        private int max;
        private int count;

        public BackgroundRunner(Form1 formtemp) {
            form = formtemp;
            timer = new System.Windows.Forms.Timer();
            timer.Interval = 100;
            timer.Tick += drawMovement;
        }

        public void run() {
            count = form.getCount();
            max = form.getMax();

            //Here happens the strange stuff:
            //If you comment out the while loop start and end 
            //and press manually the space bar the circle will move 
            //from right to left an up to down. 
            //But if you use the while loop nothing will happen 
            //and the "drawMovement" Method will never be executed, 
            //because the message inside will never be written...
            Console.WriteLine("Count: " + count + " Max: " + max);
            while (count < max) {
                Console.WriteLine("Calc Move");
                calculateMovement();

                count = form.getCount();
                max = form.getMax();
                Thread.Sleep(50);
            }
        }

        //Just a method that adds data to the stack and then start the timer
        private void calculateMovement() {
            test = new Stack<int>(5);
            test.Push(10);
            test.Push(20);
            test.Push(30);
            test.Push(40);
            test.Push(50);

            timer.Start();
        }

        //Method that goes through the stack and updates 
        //the forms point incl refresh
        private void drawMovement(object o, EventArgs e) {
            Console.WriteLine("Draw Move");
            if (test.Count <= 0) {
                timer.Stop();
                return;
            }
            int x = test.Pop();
            int y = count;
            form.setPoint(new System.Drawing.Point(x, y));
        }


    }
}

1 Ответ

0 голосов
/ 09 мая 2019

Никогда не блокируйте основной поток, в котором происходит цикл обработки сообщений.

Таймер срабатывает в сообщении, которое отправляется, поэтому ваш таймер не срабатывает, пока вы блокируете основной поток с помощью цикла и сна.

Сделайте метод async, изменив public void run() на public async void run()

Замените Threed.Sleep на await Task.Delay, тогда цикл будет работать асинхронно.

Это, вероятно, вызовет исключения, если ваше приложение предназначено для .NET Framewok версии 2.0 или новее (наверняка), потому что оно не поточно-ориентированное. Чтобы решить эту проблему, поместите все коды, которые обращаются к элементам управления пользовательского интерфейса (которые могут быть просто откуда выбрасывается исключение, на случай, если вы не уверены, что они есть.) В

form.Invoke(new Action(()=>{
    //Statements to access the UI controls
}));

Исправление

Ожидание вернет выполнение в потоке пользовательского интерфейса из-за SynchronizationContext, в котором он размещен. Поэтому не будет исключений в отношении перекрестных потоков.

от J.vanLangen

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...