многопоточность WinForms [дубликат]

189
06 апреля 2018, 15:56

На данный вопрос уже ответили:

  • Почему Thread.Sleep ведёт себя неправильно? Как мне сделать задержку или длинные вычисления в графической программе? 3 ответа

Как заставить работать вывод в Label/TextBox в многопоточном режиме?

Происходит считывание и обработка директории с файлами, чью динамику показывает Progressbar. Хочу чтобы параллельно с заполнением этой полоски прогресс бара выводилась процентовка относительно количества обработанных файлов. Однако, процент выводится только в самом конце (100%) никаких промежуточных процентов не выводится.

delegate void Print(string str);
private void Form1_Load(object sender, EventArgs e) {
    Print p1 = (string str) => { label1.Text += str; };       
    var fb =new FolderBrowserDialog();
    fb.ShowDialog();
    var dir=new DirectoryInfo(fb.SelectedPath);
    var files = dir.GetFileSystemInfos();
    progressBar1.Maximum = files.Length;
    foreach(var f in files) {
        hand(f.FullName);
        progressBar1.Value++;
        //1)
        label1.Text = Convert.ToDouble(progressBar1.Value/ progressBar1.Maximum)*100+"%";
        //2)
        //Task.Run(() => { DoSomething(); });
        //3)
        //label1.Invoke(p1, new object[] { (progressBar1.Value / progressBar1.Maximum) * 100 + "%" });
    }
}
public void DoSomething() {
    if (label1.InvokeRequired) {
        label1.Invoke(new Action(DoSomething));
    }
    else {
        label1.Text = Convert.ToDouble(progressBar1.Value / progressBar1.Maximum) * 100 + "%";
    }
}
Answer 1

Для этих целей можно использовать класс BackgroundWorker, его метод ReportProgress а также события. Пример использования ниже.

public partial class Form1 : Form
{
    BackgroundWorker bgw = new BackgroundWorker();       
    public Form1()
    {
        InitializeComponent();
        label1.Text = "";
        label2.Text = "";
    }
    private void button1_Click_1(object sender, EventArgs e)
    {
        bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
        bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
        bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
        bgw.WorkerReportsProgress = true;
        bgw.RunWorkerAsync();
    }
    void bgw_DoWork(object sender, DoWorkEventArgs e)
    {
        int total = 57; //some number (this is your variable to change)!!
        for (int i = 0; i <= total; i++) //some number (total)
        {
            System.Threading.Thread.Sleep(100);
            int percents = (i * 100) / total;
            bgw.ReportProgress(percents, i);
            //2 arguments:
            //1. procenteges (from 0 t0 100) - i do a calcumation 
            //2. some current value!
        }
    }
    void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
        label1.Text = String.Format("Progress: {0} %", e.ProgressPercentage);
        label2.Text = String.Format("Total items transfered: {0}", e.UserState);
    }
    void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
         //do the code when bgv completes its work
    }
}

UPDATE: Как верно предложил @Сергей, можно реализовать обновление на Task'ах:

private bool complete = false;
private int fileProgress = 0;
private int totalProgress = 100;
private async void button1_Click(object sender, EventArgs e)
{
    await Task.WhenAll(Task.Factory.StartNew(()=> {
        while (!complete)
        {
            if (fileProgress != 0 && totalProgress != 0)
            {
                //Here you signal the UI thread to execute the action:
                progressBar1.Invoke((Action)(() =>
                {
                    //This is done by the UI thread:
                    var a = (((double)fileProgress / (double)totalProgress) * 100);
                    progressBar1.Value = Convert.ToInt32(a);
                    label1.Text = a.ToString();
                }));
            }
        }
    }), 
    Task.Factory.StartNew(()=> {
        for (int i = 0; i < totalProgress; i++)
        {
           fileProgress++;
           Thread.Sleep(3000);
        }
        complete = true;
    } 
    ));
    MessageBox.Show("Done");  //here we're on the UI thread.
}
Answer 2

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

public class Rename
{
   ...
   public event Action<int> ProcessChanged;
   public void ABC()
   {
       ...
       ProcessChanged(i);
       ...
   }    
   ...    
}

В основном классе создаем объект подписываемся на событие

Rename.ProcessChanged += FileOper_ProccesChanged;
private void FileOper_ProccesChanged(int progress)
{
    Action action = () =>
    {
       PGBar_Music.Value = progress;
    };
    if (InvokeRequired)
          Invoke(action);
    else
          action();
}
READ ALSO
как настроить запуск unit тестов, используя Trevis CI

как настроить запуск unit тестов, используя Trevis CI

Есть консольное приложение, сборка походит в Trevis, решил написать тесты, следовательно, нужно внести изменения в файлеtravis

201
Решения для создания отчетов и печатных документов

Решения для создания отчетов и печатных документов

Есть задача по разработке ПО и большого количества отчетности к немуОтчетность как печатная (типа Отраслевых накладных и прочего), так и просто...

193
Сфотографировать 1 объект Unity

Сфотографировать 1 объект Unity

Хочу получить точное изображение террейна, со всеми деревьями и травами, в виде PNG

186