Asynchroniczne kompresowanie i pasek postępu

0

Dzień dobry,

Jestem nowy i początkujący jeżeli chodzi o c#. Niestety chyba przypadkowo wleciałem do głębokiej wody bo chciałem zrobić prosty program okienkowy, a wyszło, że nie jest to takie oczywiste.
Program po wybraniu przycisku "Aktualizuj" musi zaktualizować i skompresować dany katalog do 7z.
W trakcie wykonywania kompresji chciałbym, aby nad przyciskiem "Aktualizuj" aktualizował się w sposób obojętne jaki pasek postępu. Głównie chodzi o to, żeby użytkownik wiedział że coś się dzieje i trzeba czekać.
Napisałem program, który faktycznie kompresuje katalog, ale zawiesza się w tym czasie i pasek postępu się nie porusza.
Pogrzebałem trochę w internecie i zrozumiałem, że trzeba to zrobić w sposób asynchroniczny żeby zadziałało.
Niestety nadal nic. Kod dość mocno przerabiałem w trakcie więc może jest trochę chaotyczny.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;

namespace AplikacjaPakujaca
{
	/// <summary>
	/// Description of MainForm.
	/// </summary>
	public partial class MainForm : Form
	{
		public MainForm()
		{
			InitializeComponent();
		}
		public async void Button1Click(object sender, EventArgs e)
		{
			await ProcessFilesAsync();
			petlapaska();
			AktualizujProgres();
		}
				public async Task ProcessFilesAsync()
		{
			string sciezkapakowania = @"C:\Users\michal\Desktop\CsharpPakowanie\ZobaczymyCzyPójdzie";
			string spakowanyfolder = @"C:\Users\michal\Desktop\CsharpPakowanie.7z";
			
			Process zip = new Process();
			zip.StartInfo.FileName = @"C:\Program Files\7-Zip\7z.exe";
			zip.StartInfo.Arguments = "a -t7z \"" + spakowanyfolder + "\" \"" + sciezkapakowania + "\" -mx=9";
			zip.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
			var jakas = await Task.Run(() => zip.Start());
			//zip.WaitForExit();
				}
			private void petlapaska()
			{
			for (int i = 0; ; i--) 
			{
				Process[] procesy = Process.GetProcessesByName("7z");
				if (procesy.Length > 0)
				{
					AktualizujProgres();
				}
				else
				{
					MessageBox.Show("zakończono");
					Thread.Sleep(1000);
					break;
				}
				Thread.Sleep(2000);
			}
			ProcessStartInfo otworz = new ProcessStartInfo();
			otworz.FileName = @"C:\Users\michal\Desktop\CsharpPakowanie.7z";
			otworz.WindowStyle = ProcessWindowStyle.Maximized;
			Process otwieranie = Process.Start(otworz);
			
		}
		public void AktualizujProgres()
		{
			int p = 10;
			progressBar1.Value = p * 1;
		}		
	}
}
0

Ale ten progress bar nigdy nie będzie Ci się ruszał obecnie, bo zwyczajnie przy każdym obrocie pętli przypisujesz tam... 10.

I kuknij sobie na dokumentację ProgressBar. Tam masz nawet przykłady użycia z komentarzami wyjaśniającymi.

Value, które ustawiasz to jest tylko wartość początkowa, to Ci wyjdzie jak zajrzysz do dokumentacji :)

0

Chyba rzeczywiście tak jest jak mówisz. Zaraz postaram się to zmienić. Jednak problemu chyba to nie rozwiąże bo okno w trakcie kompresowania jest nieaktywne natomiast w menadżerze zadań " nie odpowiada"

2

W Twoim przykładzie uruchamiasz 7zip asynchronicznie, on to kompresuje i potem przechodzi do petlapaska i AktualizujProgres
Dodatkowo 7zip możesz uruchomić bez okna - 7za.exe

Nie zauważyłem zakomentowanego WaitForExit()
Generalnie jak dasz asynchroniczny sleep to nie zawiesi głównego wątku więc okno się nie zamrozi - await Task.Delay(2000)

Jabym zrobił coś w stylu: wykorzystać zdarzenia wyjścia z procesu i postępu (zakładając, że 7zip wypisuje na stdout Postęp: 10, później Postęp: 20, etc.)
Możesz jeszcze wykorzystać np. BackgroundWorkera do tego

string filePath = "ścieżka_do_programu.exe";

            try
            {
                // Tworzenie obiektu ProcessStartInfo.
                ProcessStartInfo startInfo = new ProcessStartInfo
                {
                    FileName = filePath,
                    // Możesz również ustawić inne właściwości, takie jak argumenty, zmienne środowiskowe itp.
                };

                // Tworzenie obiektu Process.
                using (Process process = new Process { StartInfo = startInfo })
                {
                    // Obsługa zdarzenia zakończenia procesu.
                    process.Exited += (s, args) =>
                    {
                        // Aktualizuj ProgressBar na końcu działania procesu.
                        progressBar.Invoke((MethodInvoker)(() => progressBar.Value = progressBar.Maximum));
                        // Tutaj możesz wykonywać inne operacje po zakończeniu procesu.
                        MessageBox.Show("Proces zakończony.", "Sukces", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    };

                    // Obsługa zdarzenia postępu procesu.
                    process.OutputDataReceived += (s, args) =>
                    {
                        if (!string.IsNullOrEmpty(args.Data) && args.Data.Contains("Postęp: "))
                        {
                            int progressValue;
                            if (int.TryParse(args.Data.Replace("Postęp: ", ""), out progressValue))
                            {
                                // Aktualizuj ProgressBar na podstawie postępu.
                                progressBar.Invoke((MethodInvoker)(() => progressBar.Value = progressValue));
                            }
                        }
                    };

                    // Rozpoczęcie procesu asynchronicznie.
                    await Task.Run(() =>
                    {
                        process.Start();
                        process.BeginOutputReadLine();
                        process.WaitForExit(); // Oczekiwanie na zakończenie procesu.
                    });
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Wystąpił błąd: {ex.Message}", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
0

Dzięki za poświęcony czas generalnie super, ale załamałem się bo jeszcze dużo nie wiem się okazuje :).
Generalnie program się uruchamia, kompresuje, ale od raz wyskakuje mi błąd z catch'a o treści "Element StandardOut nie został przekierowany lub proces jeszcze się nie rozpoczął".

0

Spróbuj uruchomić proces z opcją process.StartInfo.RedirectStandardOutput = true;

0

Jeśli menadżer zadań odpowiada, że nie odpowiada.

To znaczy, żę zablokowałeś główny wątek procesu.

Program musi się odświeżyć co kilka sekund/milisekudn jak tego nie zrobi to jest uznawany za zlagowany.

musiałeś coś dziwnie odpalić na tym asyncu, że mimo to zablokowałeś cały proces.

0

jarzi - teraz jest błąd o treści: "W celu przekierowania strumieni We/Wy, właściwość UseShellExecute obiektu Process musi mieć wartość false"
Cały kod wygląda tak, bo może coś inne źle zrobiłem :( :

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;

namespace ProgressForum
{
	/// <summary>
	/// Description of MainForm.
	/// </summary>
	public partial class MainForm : Form
	{
		public MainForm()
		{
			InitializeComponent();
		}
				public void Button1Click(object sender, EventArgs e)
		{
					ProcessFilesAsync();
		}
		
		public async Task ProcessFilesAsync()
		{
		string filePath = @"C:\Program Files\7-Zip\7z.exe";
		string sciezkapakowania = @"C:\Users\michal\Desktop\CsharpPakowanie\ZobaczymyCzyPójdzie";
		string spakowanyfolder = @"C:\Users\michal\Desktop\CsharpPakowanie.7z";

		try
		{
			//Tworzenie obiektu ProcessStartInfo.
			ProcessStartInfo startInfo = new ProcessStartInfo();
			{
				startInfo.FileName = filePath;
				startInfo.Arguments = "a -t7z \"" + spakowanyfolder + "\" \"" + sciezkapakowania + "\" -mx=9";
			};
			
			//Tworzenie obiektu Process.
			// using - w deklaracji oznacza to że zmienna lokalna jest usuwana na końcu i zostaje zwolniona pamięć komputera
			using (Process process = new Process {StartInfo = startInfo})
			{
				//Obsługa zdarzenia zakończenia procesu
				//Exited - wskazuje, że skojarzony proces zakończył się.
				// => to anonimowa metoda. Metoda bez deklaracji. Skrót pozwala na napisanie w miejscu w którym chcemy jej użyć
				process.Exited += (s, args) =>
				{
					//Aktualizuj progressbar na końcu działania procesu
					//Delegaty dbają o bezpieczeństwo typów zwracanych obiektów i metod, na które wskazują.
					//Invoke wykonuje określonego delegata w wątku
					progressBar1.Invoke((MethodInvoker)(() => progressBar1.Value = progressBar1.Maximum));
					//Tutaj mogę wykonywać inne operacje po zakończeniu procesu
					MessageBox.Show("Proces zakończony.", "Sukces", MessageBoxButtons.OK, MessageBoxIcon.Information);
				};
				
				//Obsługa zdarzenia postępu procesu.
				//&& oznacza AND
				process.OutputDataReceived += (s, args) =>
				{
					if (!string.IsNullOrEmpty(args.Data) && args.Data.Contains("Postęp: "))
					{
						int progressValue;
						if (int.TryParse(args.Data.Replace("Postęp: ", ""), out progressValue))
						{
							//Aktualizuj ProgressBar na podstawie postępu.
							progressBar1.Invoke((MethodInvoker)(() => progressBar1.Value = progressValue));
							
						}
					}
			};
			
			//Rozpoczęcie procesu asynchronicznie.
			//Thread.Sleep(2000);
			await Task.Run(() =>
			               {
			               	process.StartInfo.RedirectStandardOutput = true;
			               	process.Start();
			               	process.BeginOutputReadLine();
			               	process.WaitForExit(); //Oczekiwanie na zakończenie procesu.
			               });
			}
		}
		catch (Exception e)
		{
			MessageBox.Show("Wystąpił błąd:" + e.Message, "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error);
		}
		}

	}
}
0

No to dodaj process.StartInfo.UseShellExecute = false; ;)

0

Próbowałem, otwiera się pusta konsola 7 zip i zostaje utworzony plik 7z.tmp, który nie jest uzupełniany danymi jego wielkość wynosi 4 KB.
Ja jeszcze próbuję inaczej to zrobić czyli uruchomić animację "Marquee" po naciśnięciu przycisku i zakończeniu animacji po zamknięciu procesu 7-zip - prawie działa, jak skończe to wstawie kod.
13:45 - nie działa. uruchamiam animację paska, ale po zakończonym pakowaniu, nie mogę paska zatrzymać ehh. Jak zrobię jakąś pętle for lub if to generalnie się aplikacja zawiesza.
jarzi próbowałem jeszcze kombinować z Twoim kodem cały czas te same objawy. Katalog po pewnym czasie z otworzonym pustym oknem konsoli 7zip się kompresuje, jednak pasek postępu nic nie pokazuje.
Zrobiłem w ten sposób na razie mi wystarczy aczkolwiek jakbyście mieli lepszy pomysł dajcie znać:

namespace Progress
{
	/// <summary>
	/// Description of MainForm.
	/// </summary>
	public partial class MainForm : Form
	{
		public MainForm()
		{
			InitializeComponent();
		}
		
		public async void button1Click(object sender, EventArgs e)
		{
		string filePath = @"****\7z.exe";
		string sciezkapakowania = @"****\ZobaczymyCzyPójdzie";
		string spakowanyfolder = @"****\CsharpPakowanie.7z";

		try
		{
			//Tworzenie obiektu ProcessStartInfo.
			ProcessStartInfo startInfo = new ProcessStartInfo();
			{
				startInfo.UseShellExecute = false;
			    startInfo.RedirectStandardOutput = true;
			    startInfo.CreateNoWindow = true;
				startInfo.FileName = filePath;
				startInfo.Arguments = "a -t7z \"" + spakowanyfolder + "\" \"" + sciezkapakowania + "\" -mx=9";
				progressBar1.Style = ProgressBarStyle.Marquee;
				progressBar1.MarqueeAnimationSpeed = 20;
			}

			using (Process process = new Process {StartInfo = startInfo})
			await Task.Run(() =>
			               {
			               	process.Start();
			               	process.WaitForExit(); 
			               });
			progressBar1.Style = ProgressBarStyle.Continuous;
			progressBar1.MarqueeAnimationSpeed = 0;
			
			ProcessStartInfo otworz = new ProcessStartInfo();
			otworz.FileName = @"***\CsharpPakowanie.7z";
			otworz.WindowStyle = ProcessWindowStyle.Maximized;
			Process otwieranie = Process.Start(otworz);
			//Application.Exit();
			}
		catch (Exception ex)
		{
			MessageBox.Show("Wystąpił błąd:" + ex.Message, "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error);
		}
		}

	}
}
0

co do progresu to zainteresuj sie interfejsem IProgress.
Spojrz tutaj: https://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html

1 użytkowników online, w tym zalogowanych: 0, gości: 1