c# - example - congelar la interfaz al intentar actualizar datagridview
datagridview c# windows form (1)
Respuesta corta: no hay nada incorrecto con su código. El diseño no es bueno
Respuesta larga
¿Has oído a alguien decir "solo puedo hacer una cosa a la vez"? Bueno, eso es lo que está pasando aquí. Su código de aplicación de formularios de Windows se está ejecutando por 1 hilo y ese hilo solo puede hacer una cosa a la vez. Cuando está haciendo ping, espera la respuesta. Si la respuesta es exitosa, entonces copia un archivo. Como tiene un bucle, lo sigue haciendo hasta que haya completado todas las filas.
Mientras lo hace, probablemente esté haciendo clic en otras cosas en la interfaz de usuario, pero su hilo "solo puede hacer 1 cosa a la vez". Está ocupado haciendo las cosas en el bucle. Por lo tanto, cuando haces clic, solo tienes que esperar.
Entonces, ¿cómo lo arreglo para que la IU no se congele?
En lenguaje sencillo, necesitas hacer esto. Imagina que eres un hilo:
Soy el hilo de la interfaz de usuario y mi objetivo final es mantener la IU receptiva. No quiero que la IU se congele. Por lo tanto, si tengo que hacer algo, excepto el trabajo de UI, voy a pedirle a otra persona que lo haga. Mientras ese otro está haciendo otro trabajo, seré libre de hacer el trabajo de UI.
Ese otro es otro hilo. Aquí hay un ejemplo, lea mis comentarios en el código y aplíquelo a su aplicación. Si desea ejecutar este código, cree un formulario Form1
con una etiqueta llamada label1
y dos botones. Asignar ButtonClickHandlerAsync
al controlador de clic de un botón y Stop_Click
al otro botón.
Toda la acción comienza cuando haces clic en el botón que ejecuta ButtonClickHandlerAsync
. Mientras está haciendo el trabajo, puede hacer clic en el otro botón y se mostrará un cuadro de mensaje y permanecer atento. Por cierto, he copiado este código desde aquí, pero agregué mis propios comentarios dentro del código para que sepas lo que está sucediendo.
public partial class Form1 : Form {
// We need this because this will allow us to interact with UI controls. UI controls can only be accessed by the thread that
// created the UI control. In this case it is the thread which started the application so the main thread.
private readonly SynchronizationContext synchronizationContext;
private DateTime previousTime = DateTime.Now;
public Form1() {
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
}
private void Stop_Click(object sender, EventArgs e) {
// I am the UI thread. I can do this because T2 is helping me do the loop.
MessageBox.Show( "I am doing other things." );
}
private async void ButtonClickHandlerAsync(object sender, EventArgs e) {
button1.Enabled = false;
var count = 0;
// I am the UI thread. I have other things to do. So please run this loop by using a thread from the thread pool.
// When you are done running the loop let me know (This is what the await does)
// I am the UI thread so I am going to return back from right here
// to the point where ButtonClickHandlerAsync was called from. (it was called by a click, so when it returns it will have nothing
// to do. Therefore, it will be ready to react to another UI job such as another click or update the UI etc.
await Task.Run( () =>
{
// I am a thread from the thread pool. My name is T2. I am helping the UI thread so the UI thread can do other things.
for( var i = 0; i <= 5000000; i++ ) {
UpdateUI( i );
count = i;
}
} );
// I am the UI thread. Ok looks like the loop is done. So I will do the following 2 lines of work
label1.Text = @"Counter " + count;
button1.Enabled = true;
}
public void UpdateUI(int value) {
// I am T2. I am helping the UI thread.
var timeNow = DateTime.Now;
if( ( DateTime.Now - previousTime ).Milliseconds <= 50 )
return;
// I do not have access to the UI controls since I did not create them. So I am just going to ask the synchronizationContext
// to do this for me by giving it a SendOrPostCallback
synchronizationContext.Post( new SendOrPostCallback( o =>
{
// I am the UI thread. I will do this.
label1.Text = @"Counter " + ( int ) o;
} ), value );
// I am T2. I will do this and then return and do more work.
previousTime = timeNow;
}
¿Cómo puedes arreglar tu código?
Puede hacer CheckOffice
y copiar los archivos usando un hilo del threadpool. Ese hilo puede usar el synchronizationContext
para si necesita interactuar con la UI. El hilo principal de UI puede mantenerse libre para hacer otras cosas mientras el hilo del grupo de subprocesos está revisando la oficina y copiando un archivo, lo que podría llevar mucho tiempo, especialmente si el archivo es grande.
"Soy el hilo de UI. No tengo tiempo para esperar una respuesta de ping". "Soy el hilo de la interfaz de usuario. No tengo tiempo para copiar un archivo de una ubicación a otra, lo que puede llevar segundos o minutos. Mi trabajo consiste en mantener la IU receptiva".
EDITAR
Escribí la respuesta anterior antes de que el OP escribiera la restricción de .NET 4. Pero estoy bastante seguro de que crearon un paquete NuGet para esto. Mira aquí y aquí .
Si no puede usar async
y await
, los mismos conceptos anteriores se aplican al enhebrado.
Thread t2 = new Thread( new ThreadStart(() =>
{
for( var i = 0; i <= 5000000; i++ ) {
UpdateUI( i );
count = i;
}
} ) );
t2.Start();
Estoy usando el código a continuación para copiar archivos y configurar la columna de estado en datagridview
para informar al usuario que la conexión está activa pero cuando presiono el botón para ejecutar es el método de congelación de la interfaz ...
He buscado mucho, sé que usando task.run();
no es posible porque no está incluido en .not 4
no es una nueva característica de .net 4.5
. También sé que Task.Factory.StartNew();
se puede usar en lugar de usar task.run () pero tiene mucho riesgo como hilo implícito y sé que usar threading explícito también es bueno elegir un
Quiero un poco de ayuda para continuar con mi proyecto y seguir aprendiendo en lugar de apilarme en ese punto aburrido
public void PatchUpdates()
{
try
{
foreach (DataGridViewRow OfficeListRow in DGV_OfficeList.Rows)
{
string OfficeIPAddress = OfficeListRow.Cells[3].Value.ToString();
foreach (DataGridViewRow FileListRow in DGV_FileList.Rows)
{
string SoruceFileNamePath = FileListRow.Cells[4].Value.ToString();
string DestinationFileNamePath = @"//" + OfficeIPAddress + @"/usb1_1/test/" + Path.GetFileName(SoruceFileNamePath);
//check if connection to remote server is available
var vResult = CheckOffice(OfficeIPAddress);
if (vResult == 1)
{
DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "Connected";
File.Copy(SoruceFileNamePath, DestinationFileNamePath, true); //copy files...
}
else if (vResult == 0)
{
DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "disconnected";
break;
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
verifica el código de la oficina debajo
public int CheckOffice(string _ipAddress)
{
int timeout = 120;
string data = "PingTestData";
byte[] buffer = Encoding.ASCII.GetBytes(data);
Ping PingSender = new Ping();
PingOptions options = new PingOptions();
options.DontFragment = true;
PingReply reply = PingSender.Send(_ipAddress, timeout, buffer, options);
if (reply.Status == IPStatus.Success)
{
return 1;
}
else
{
return 0;
}
}
a continuación, he intentado hacer un hilo, pero eso no resolverá mi problema
public void PatchUpdates()
{
try
{
foreach (DataGridViewRow OfficeListRow in DGV_OfficeList.Rows)
{
string OfficeIPAddress = OfficeListRow.Cells[2].Value.ToString();
foreach (DataGridViewRow FileListRow in DGV_FileList.Rows)
{
string SoruceFileNamePath = FileListRow.Cells[4].Value.ToString();
string DestinationFileNamePath = @"//" + OfficeIPAddress + @"/usb1_1/test/" + Path.GetFileName(SoruceFileNamePath);
Thread foregroundthread = new Thread(() => CheckOffice(OfficeIPAddress));
foregroundthread.Start();
//check if connection to remote server is available
if (CheckOffice(OfficeIPAddress) == 1)
{
DGV_OfficeList[3, DGV_OfficeList.CurrentCell.RowIndex].Value = "Connected";
//file.copy(sorucefilenamepath, destinationfilenamepath, true); //copy files...
}
else if (CheckOffice(OfficeIPAddress) == 0)
{
DGV_OfficeList[3, DGV_OfficeList.CurrentCell.RowIndex].Value = "disconnected";
break;
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
También he intentado esto, pero como he dicho, thask.run no está disponible en dot net 4
var task = Task.Run(() =>
{
var result = CheckOffice(OfficeIPAddress);
this.BeginInvoke((Action)(() =>
{
if (result == 1)
{
DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "Connected";
//file.copy(sorucefilenamepath, destinationfilenamepath, true); //copy files...
}
else if (result == 0)
{
DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "disconnected";
}
}));
}
);
-------------------------------------------------- ------- Actualización ------------------------------------------ ---------------
public void PatchUpdates()
{
try
{
foreach (DataGridViewRow OfficeListRow in DGV_OfficeList.Rows)
{
string OfficeIPAddress = OfficeListRow.Cells[3].Value.ToString();
int RowNum = OfficeListRow.Index;
foreach (DataGridViewRow FileListRow in DGV_FileList.Rows)
{
string SoruceFileNamePath = FileListRow.Cells[4].Value.ToString();
//string DestinationFileNamePath = @"//" + OfficeIPAddress + @"/usb1_1/test/" + Path.GetFileName(SoruceFileNamePath);
string DestinationFileNamePath = @"F:/test/" + Path.GetFileName(SoruceFileNamePath); //TestPurpose
Thread t2 = new Thread(new ThreadStart(() =>
{
int vResult = CheckOffice(OfficeIPAddress);
UpdateUI(vResult, RowNum, SoruceFileNamePath, DestinationFileNamePath, OfficeIPAddress);
}));
t2.Start();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
El método UpdateUI para actualizar la interfaz de usuario ...
public void UpdateUI(int vResult, int RowNum, string SoruceFileNamePath, string DestinationFileNamePath,string OfficeIPAddress)
{
try
{
var timeNow = DateTime.Now;
if ((DateTime.Now - PreviousTime).Milliseconds <= 10)
return;
SynchronizationContext.Post(new SendOrPostCallback(o =>
{
if (vResult == 1)
{
DGV_OfficeList[4, RowNum].Value = "Connected";
//File.Copy(SoruceFileNamePath, DestinationFileNamePath, true);
//MessageBox.Show("Pingable " + OfficeIPAddress); //TestPurpose
}
else if (vResult == 0)
{
DGV_OfficeList[4, RowNum].Value = "Disconnected";
//MessageBox.Show("Not reachable"); //TestPurpose
}
}), vResult);
PreviousTime = timeNow;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}