Как мне избежать ужасного Application.DoEvents() при многопоточности [закрыто]

StackOverflow https://stackoverflow.com//questions/21033504

Вопрос

Итак, я читал много статей, в которых не одобрялось использование Application.DoEvents() и даже говорилось, что его никогда не следует использовать, но я не могу найти хорошей альтернативы для моего сценария...Приложение, над которым я работаю, имеет метод, который вызывается событием this.Shown при первом запуске основной формы графического интерфейса.Метод выполняет некоторую работу, которая занимает около минуты, поэтому тот же метод также создает форму, которая по сути является пользовательским индикатором выполнения.Имейте в виду, что этот процесс в настоящее время является однопоточным, поэтому, когда этот метод выполняет работу, основной графический интерфейс и индикатор выполнения перестают отвечать на запросы.Если пользователь в это время щелкнет где-нибудь, экран погаснет.Поэтому я работаю над тем, чтобы перенести часть работы, которую выполняет этот метод, в поток BackgroundWorker.Вот что я придумал:

private BackgroundWorker Bgw = new BackgroundWorker();
private int LoadPercentage = 0;

    //this sub is executed on the main UI thread
    public void RunBgw()
    {
        bool StopThread = false;
        //this object should be created in this method and needs to be updated as the child thread is doing work
        MyCustomDialog dialog = new MyCustomDialog();  

        dialog.UpdateProgress(0, "My message");

        dialog.Show();
        this.Invalidate();
        this.Refresh();

        //critical properties to set if you want to report progress/be able to cancel the operation
        Bgw.WorkerSupportsCancellation = true;
        Bgw.WorkerReportsProgress = true;

        //add handlers to Bgw so events will fire
        Bgw.DoWork += new DoWorkEventHandler(Bgw_DoWork);
        Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);
        Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);

        //fire off thread
        Bgw.RunWorkerAsync();

        while (Bgw.IsBusy == true) 
        {
            if (BW.CancellationPending == true) 
            {
                StopThread = true;
                break;
            }
            Application.DoEvents();

            if(LoadPercentage == 10) 
            {
                dialog.UpdateProgress(LoadPercentage, "Still working...");
                this.Invalidate();
                this.Refresh();
            }
            if(LoadPercentage == 50) 
            {
                dialog.UpdateProgress(LoadPercentage, "Halfway done...");
                this.Invalidate();
                this.Refresh();
            }
            // etc...

            //slow down loop so it doesnt take up all the CPU
            Thread.Sleep(200);
        }

        if(!StopThread) {
            //continue with something else.
        }
    }

    private void Bgw_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker BgwLocal = sender as BackgroundWorker;

        if ((BgwLocal.CancellationPending == true))
        {
            e.Cancel = true;
            break;
        }
        else
        {
            TimeConsumingWork();
            BgwLocal.ReportProgress(10); //report progress back to the main UI thread
            TimeConsumingWork();
            BgwLocal.ReportProgress(15, SomeGuiIcon); //pass back specific gui icon
            TimeConsumingWork();
            BgwLocal.ReportProgress(50); 

            // etc...  
        }
    }

    private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        LoadPercentage = e.ProgressPercentage; //set current percentage of progress

        if(LoadPercentage == 15)
        {
            GuiIcon1 = (Icon)e.UserState; //set gui icon
        }
    }

    private void Bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if ((e.Cancelled == true))
        {
            //error handling
        }
        else if (!(e.Error == null))
        {
            //error handling
        }
        else
        {
            //success
        }
    }

Все работает хорошо, за исключением того, что обработка ошибок оказалась сложной и запутанной.Есть ли лучший способ работы с потоками при обновлении существующего объекта в основном потоке??

Спасибо за прочтение.

Это было полезно?

Решение

Вам не следует блокировать поток пользовательского интерфейса с помощью этого кода:

while (Bgw.IsBusy == true) { ... }

Вместо этого позвольте RunBgw() чтобы вернуться к вызывающему абоненту.Используйте события, присутствующие в BackgroundWorker, чтобы узнать, когда он завершился.Конкретно

Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);

о ходе работы сообщает по телефону

Bgw_ProgressChanged

и

Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);

причины

Bgw_RunWorkerCompleted

который будет вызван после завершения работы BackgroundWorker.

Обновите индикатор выполнения изнутри Bgw_ProgressChanged.

Пользовательский интерфейс Windows управляется событиями.Ваш код не использует события для управления выполнением программы.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top