View Full Version : [C#] - ProgresBar e label durante un ciclo non aggiornate
mirkus90
27-02-2014, 19:43
Salve,
Ho un problemino...premetto che sono agli albori di c# e mi sono cimentato in questo esercizietto. Dovrei aspettare 5 secondi e durante questa attesa mostrare l'avanzamento di una barra di progresso. Ciò che noto che l'aggiornamento non me lo fa in "tempo reale"...perchè ho notato che mettendo una modifica della label all'interno del ciclo, questa non viene modificata
DateTime start = DateTime.Now;
int stop ;
int valore;
stop=start.Second+5;
progressBar1.Maximum=stop-1;
progressBar1.Minimum=start.Second;
valore=start.Second;
while(valore<stop){
progressBar1.Value=valore;
start = DateTime.Now;
if(valore != start.Second){
valore=valore+1;
}
label1.Text="dentro";
}
label1.Text="fine";
Come si nota dal codice quando entra nel ciclo e mentre la progresbar progredisce si la label1 dovrebbe cambiare in "dentro" e una volta uscito dal ciclo dovrebbe cambiare in "fine" ma in realtà avviene il cambiamento solo in "fine".
Come può mai accadere questo?
[Kendall]
01-03-2014, 09:05
Salve,
Ho un problemino...premetto che sono agli albori di c# e mi sono cimentato in questo esercizietto. Dovrei aspettare 5 secondi e durante questa attesa mostrare l'avanzamento di una barra di progresso. Ciò che noto che l'aggiornamento non me lo fa in "tempo reale"...perchè ho notato che mettendo una modifica della label all'interno del ciclo, questa non viene modificata
DateTime start = DateTime.Now;
int stop ;
int valore;
stop=start.Second+5;
progressBar1.Maximum=stop-1;
progressBar1.Minimum=start.Second;
valore=start.Second;
while(valore<stop){
progressBar1.Value=valore;
start = DateTime.Now;
if(valore != start.Second){
valore=valore+1;
}
label1.Text="dentro";
}
label1.Text="fine";
Come si nota dal codice quando entra nel ciclo e mentre la progresbar progredisce si la label1 dovrebbe cambiare in "dentro" e una volta uscito dal ciclo dovrebbe cambiare in "fine" ma in realtà avviene il cambiamento solo in "fine".
Come può mai accadere questo?
Quando si programmano applicativi con interfaccia grafica una delle cose da tenere sempre a mente è che l'utente NON vuole che l'interfaccia si blocchi, a prescindere dal motivo.
Il tuo codice è bloccante, quindi l'utente dovrà aspettare tutti i 5 secondi prima di poter interagire ancora con l'interfaccia.
Se nei test che stai facendo questo può non essere un problema, lo è in una applicazione reale, e per questo è buona norma abituarsi a scrivere il nostro codice con un certo occhio a questi fattori.
Nel tuo caso puoi rendere il tutto non bloccante, e decisamente più elegante nella scrittura, utilizzando un timer, e collegando al relativo evento "Elapsed" un gestore che si occupa di aggiornare l'interfaccia ogni volta che tale evento viene scatenato (puoi impostare tu il valore degli intervalli).
Oltre ad essere una maniera più corretta (in prospettiva futura) di affrontare il problema, è anche estremamente più performante che non gestire il tutto dentro un loop il cui codice, prima del completamente, viene eseguito una infinità di volte inutilmente.
Questo un esempio basilare:
public partial class MainWindow : Window
{
public Timer timer;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
timer = new System.Timers.Timer(10);
timer.Elapsed += (senderObj, eventArgs) => {
// Fai qualcosa
};
timer.Start();
}
}
In questo caso vedi solo l'evento che scatena l'inizio del tutto, chiaramente da qualche parte ci sarà anche l'evento che lo farà finire.
Verosimilmente se il tutto è basato esclusivamente sul tempo puoi istanziarti uno StopWatch, farlo partire quando ne hai la necessità insieme al Timer, e all'interno dell'evento Elapsed verificare il tempo trascorso.
mirkus90
01-03-2014, 12:15
E se invece (dato che parlando con il "cliente" una barra progressiva non serve più di tanto) utilizzassi un secondo thread per avviare le operazioni da fare in modo che il secondo thread esegua le operazioni (invio dati via seriale) e il thread principale resti sempre attivo in modo che al click sul pulsante lo possa fermare. Io ho pensato di fare in questo modo, ma non sono sicuro che funzioni perchè noto che quando chiudo il thread continua a generare thread
public class SendData
{
// This method will be called when the thread is started.
public void DoWork()
{
byte[] ON = new byte[1] { 0x64 };
byte[] OFF = new byte[1] { 0x6e };
byte[] to_write = new byte[1] { 0x64 };
int stop;
int attuale;
DateTime start;
try{
serialPort.Open();
}
catch (Exception ex){
Console.WriteLine("open() error: " + ex.Message);
Environment.Exit(1);
}
while (!_shouldStop)
{
start = DateTime.Now;
stop=start.Second+5;
attuale=start.Second;
while(attuale<stop){
start = DateTime.Now;
if(attuale != start.Second){
attuale=attuale+1;
}
}
serialPort.Write(to_write,0,1);
if(to_write==ON){
to_write=OFF;
}
else{
to_write=ON;
}
}
serialPort.Close();
}
public void RequestStop()
{
_shouldStop = true;
}
private volatile bool _shouldStop;
SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
}
namespace prova
{
/// <summary>
/// Description of MainForm.
/// </summary>
///
public partial class MainForm : Form
{
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
string[] serialPorts = SerialPort.GetPortNames();
foreach (string serialPort in serialPorts){
this.cbSerialBox.Items.Add(serialPort);
if(serialPort.Length != 0)
cbSerialBox.SelectedIndex = 0;
}
}
void Button1MouseClick(object sender, MouseEventArgs e)
{
SendData workerObject = new SendData();
Thread workerThread = new Thread(workerObject.DoWork);
if(button1.Text=="start"){
label1.Text="main thread: Starting worker thread...";
workerThread.Start();
button1.Text="stop";
}
else{
workerObject. RequestStop();
workerThread.Abort(workerObject);
button1.Text="start";
}
}
}
}
P.s.: Ho utilizzato il mio metodo di conteggio del tempo perchp preferisco prima far funzionare i thread per bene
[Kendall]
01-03-2014, 14:37
Ora però la domanda è totalmente diversa da quella iniziale, ergo il problema cambia completamente.
Allora, come primo consiglio l'utilizzo della classe Thread è sconsigliabile.
È una classe che va ad agire a "basso" (le virgolette sono d'obbligo) livello.
Utilizza sempre, salvo rari classi, la sua astrazione, la classe Task, che gestisce diverse delle problematiche derivanti dal multithreading.
Seconda cosa, quando si parla di threading e cancellazione, la richiesta che fai alla funzione Abort() non puoi avere la certezza che venga eseguita (per una serie di motivi).
Quando si parla di multithreading e/o operazioni asincrone, si entra in un campo decisamente complesso (personalmente il più ostico che ho trovato finora nello studio della programmazione). Ti conviene studiare adeguatamente il tutto.
Solitamente, quando si avvia una operazione asincrona e si vuole avere la possibilità di cancellarla, si utilizza la tecnica dei "cancellation token", che è in parte simile a quanto hai scritto tu prendendo spunto dall'msdn.
Ti consiglio questa lettura, in passato mi è stata decisamente utile: http://blogs.msdn.com/b/csharpfaq/archive/2010/07/19/parallel-programming-task-cancellation.aspx
Ultimo avviso: basarsi sulla label di un bottone per avviare/fermare/riavviare una operazione è decisamente sbagliato. Quando tu richiedi la cancellazione e cambi subito la label del bottone, non puoi essere sicuro che l'operazione asincrona sia conclusa. Pertanto, se tu ripremi il pulsante "Start" può benissimo essere che nel frattempo l'operazione precedente sia ancora in esecuzione.
In questi casi solo ed esclusivamente l'effettiva terminazione dell'operazione asincrona dovrebbe riabilitare il pulsante per iniziarne una di nuova, e tutte queste tecniche si riconducono alla corretta gestione dei Thread.
Quindi, riassumendo, prima di iniziare questa strada, impara per bene tutto ciò che ci sta dietro, acquista un volume (da questo punto di vista ti consiglio tutta la vita "C# in a Nutshell", acquisto che rifarei mille volte) o segui molte guide online, ma finchè non hai ben chiara la situazione non avventurarti in questo campo perchè potresti fare più danni che altro alla tua applicazione.
mirkus90
01-03-2014, 17:25
Ok...letto e compreso...ora mi sorge un piccolo dubbio...ovvero non riesco a trovare nulla che riesca ad aiutarmi...
Ho creato il mio bel taskA che mi genere quella specie di "loop" (ho assegnato a taskA il metodo doWork)...quando richiamo il mio RequestStop(), questo metodo dovrebbe cambiare la variabile nel doWork che viene eseguito nel taskA...in modo che termina la sua esecuzione e possa terminare il task...solo che non riesco a richiamare nel modo corretto il metodo RequestStop in modo che cambi quando voglio io
Potresti provare fermando il thread in esecuzione. :)
Sent from my GT-I8750 using Tapatalk
[Kendall]
02-03-2014, 08:09
Ok...letto e compreso...ora mi sorge un piccolo dubbio...ovvero non riesco a trovare nulla che riesca ad aiutarmi...
Ho creato il mio bel taskA che mi genere quella specie di "loop" (ho assegnato a taskA il metodo doWork)...quando richiamo il mio RequestStop(), questo metodo dovrebbe cambiare la variabile nel doWork che viene eseguito nel taskA...in modo che termina la sua esecuzione e possa terminare il task...solo che non riesco a richiamare nel modo corretto il metodo RequestStop in modo che cambi quando voglio io
`
La parte incriminata è questa:
void Button1MouseClick(object sender, MouseEventArgs e)
{
SendData workerObject = new SendData();
Thread workerThread = new Thread(workerObject.DoWork);
if (button1.Text=="start") {
label1.Text="main thread: Starting worker thread...";
workerThread.Start();
button1.Text="stop";
}
else {
workerObject. RequestStop();
workerThread.Abort(workerObject);
button1.Text="start";
}
}
Qui vai a creare ad ogni click del pulsante un nuovo thread (o Task se hai modificato in tal senso), quindi quel RequestStop() andrà sempre e solo ad agire nel nuovo Task creato, non quello originario del primo click.
Il problema pertanto è che il Task NON và dichiarato lì, ma come campo privato della classe, così che ogni azione venga fatta sempre su di esso.
All'interno del pulsante andrai solo ad istanziare il nuovo Task e SOLO SE un altro non è già attivo (quindi non all'inizio del corpo dell'evento ButtonClick).
PS: Io userei come ti accennavo prima un approcio diverso comunque. E precisamente lo sfruttare i meccanismi di asincronia resi disponibili dal linguaggio.
Fai una ricerca riguardo alle parole async e await (in relazione al c#), e molte cose ti saranno più chiare.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.