I've created a new WinForms project in Visual Studio 2017.
Then I've added a button and textbox to Form1 (screenshot).
Code:
using System;
using System.Net;
using System.Windows.Forms;
namespace TestWinForms
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private delegate void FormDelegate();
private void button1_Click(object sender, EventArgs e)
{
UseWaitCursor = true;
button1.Enabled = false;
BeginInvoke(new FormDelegate(delegate
{
using (WebClient web = new WebClient())
{
web.Encoding = System.Text.Encoding.UTF8;
textBox1.Text = web.DownloadString("https://stackoverflow.com/");
}
UseWaitCursor = false;
button1.Enabled = true;
}));
}
}
}
When I click button1 window cursor isn't changing to WaitCursor, and when I hover cursor over ControlBox buttons they aren't "glowing". In short, BeginInvoke() blocks main thread for a moment. Why this is happening and how can I avoid it?
As the fellow users said in the comments, it's DownloadString that's blocking your UI, not BeginInvoke since it waits for the download to complete.
You should probably tackle this in another way, by using DownloadStringAsync:
private WebClient _web;
private void button1_Click(object sender, EventArgs e)
{
UseWaitCursor = true;
button1.Enabled = false;
_web = new WebClient();
_web.Encoding = System.Text.Encoding.UTF8;
_web.DownloadStringCompleted += DownloadCompleted;
_web.DownloadStringAsync("https://stackoverflow.com/");
}
private void DownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
textBox1.Text = e.Result;
UseWaitCursor = false;
button1.Enabled = true;
_web.Dispose();
}
I second Hans comment : the BeginInvoke only defer execution later.
What you need is either a BackgroundWorker or (better) using the async/await pattern:
private async void button1_Click(object sender, EventArgs e)
{
UseWaitCursor = true;
button1.Enabled = false;
using (WebClient web = new WebClient())
{
web.Encoding = System.Text.Encoding.UTF8;
textBox1.Text = await web.DownloadStringTaskAsync("https://stackoverflow.com/");
}
UseWaitCursor = false;
button1.Enabled = true;
};
}
The DownloadStringTaskAsync will run on a worker process because it is awaitable. While it runs, the UI thread will continue to process the other events anyway, then continue its execution after the await statement as the DownloadStringTaskAsync finishes.
Related
I'm trying to save a rather large text file when the user hits the save button. It can be up to 30MBs. After pressing the button, I'd like the texbox to display "Saving..." as it's saving the file and when it completes, display "Saved". However I can't get this to work. I've tried using Task.run, await task.Run, and using a background worker. All these options hang the UI until the save completes. The textbox does not display "Saving..." until after it saves and the program is unresponsive until then. How can I fix this?
private async void btnSave_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.ShowDialog();
// If the file name is not an empty string open it for saving.
if (saveFileDialog1.FileName != "")
{
logFileName = saveFileDialog1.FileName;
btnOpenFile.IsEnabled = false;
btnSave.IsEnabled = false;
tbText1.Text += "\n\n***Saving...***\n";
tbText1.ScrollToEnd();
await Task.Run(() => File.WriteAllText(logFileName, Results.ToString()));
tbText1.Text += "\n\n***SAVED***\n\n";
tbText1.ScrollToEnd();
btnOpenFile.IsEnabled = true;
btnSave.IsEnabled = true;
}
As discussed in the comments, the problem is with Results.ToString().
I tried to reproduce the issue with this code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
for (int i = 1; i < 40536; i++)
{
stringBuilder.Append(new string('a', i));
}
}
readonly StringBuilder stringBuilder = new StringBuilder();
int tickNumber = 0;
private void sync_Click(object sender, EventArgs e)
{
button1.Enabled = false;
stringBuilder.ToString();
button1.Enabled = true;
}
private async void async_Click(object sender, EventArgs e)
{
button2.Enabled = false;
await Task.Run(() => stringBuilder.ToString());
button2.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
tickNumber %= 50;
tickNumber++;
label1.Text = new string('.', tickNumber);
}
}
But it works as expected:
Sometimes UI hands for a little bit though. Is this what are you talking about?
Try moving code that generates contents for StringBuilder inside the task (so this StringBuilder only exists in background thread)
Before discussing my problem, I want to know you about my stage.I am very new to C# programming. It is the first time I am working on it. So my knowledge in C# is very minimum.
I am developing my application using Windows Forms in C#. At some instant I was supposed to run 5 operations simultaneously . So that I tried the BackGroundWorker Component in the ToolBox of C#.
But using that, I could process only one of my operations among 5. I tried using 5 BackGroundWorker Components from the ToolBox and defined the DoWork functions seperately.
But when I called the RunWorkerAsync() function it threw an error saying "backgroundworker is currently busy and cannot run multiple tasks concurrently".
I don't whether I can use multiple BackGroundWorker in my program.Creating array didn't help me. Because I have a infinite loop to be run inside the DoWork functions of the BackGroundWorker components. If there is any other way to run 5 operations at the same time, Please help me get an idea about that. Thanks in advance.
Here is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespace myapp5
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw1.WorkerSupportsCancellation = true;
bw1.WorkerReportsProgress = true;
bw1.DoWork += new DoWorkEventHandler(bw1_DoWork);
}
BackgroundWorker bw = new BackgroundWorker();
BackgroundWorker bw1 = new BackgroundWorker();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if ((worker.CancellationPending == true))
{
e.Cancel = true;
}
else
{
try
{
videostream();
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
}
}
private void bw1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if ((worker.CancellationPending == true))
{
e.Cancel = true;
}
else
{
try
{
videostream();
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
}
}
private void button2_Click(object sender, EventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
else
{
MessageBox.Show("Background Worker is busy");
}
}
private void button3_Click(object sender, EventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
bw.Dispose();
MessageBox.Show("Background Closed");
}
}
private void button4_Click(object sender, EventArgs e)
{
if (bw1.IsBusy != true)
{
bw1.RunWorkerAsync();
}
else
{
MessageBox.Show("Background Worker is busy");
}
}
private void button5_Click(object sender, EventArgs e)
{
if (bw1.WorkerSupportsCancellation == true)
{
bw1.CancelAsync();
bw1.Dispose();
MessageBox.Show("Background Closed");
}
}
}
}
If you want to perform 5 different function parallel than you need to 5 backgroundworker thread for that. One Backgroupwork thread is not sufficient for running 5 different function parallel.
You can also you TPL(taksparallel) library for this instead of backgroundworker.
You should try thread for nominal use.
private object threadUnsafeObject;
private object locker = new object();
private void RunMulitpleThread()
{
object pass_your_parameter_here = "";
for (int iCount = 0; iCount < 5; iCount++)
{
System.Threading.Thread thread = new System.Threading.Thread(DoWork);
thread.Start(pass_your_parameter_here);
}
}
private void DoWork(object parameters)
{
lock (locker)
{
threadUnsafeObject = parameters;
}
}
If you're using .net 4.0 or above, you should use Tasks. This is the direction where the .NET framework is moving. I've set up two fiddles for you to check out.
If you're using .NET 4.0 : https://dotnetfiddle.net/GUxD2j.
If you're using .NET 4.5: https://dotnetfiddle.net/NirDuU. This sample shows the use of the new await/ async pattern introduced in the .NET framework
I think that your program have logical error so the same BackgroundWorker instance is used five times for calling RunWorkerAsync(). Review the code for this.
UPD
Your DoWork handler must look like this:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (true)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// perform work partially with short time!
// and break the while loop at the end
}
}
}
UPD-2
Don't use Dispose() for workers.
I've looked in many places for this but still haven't found a solution. What I'm trying to achieve is being able to use BackgroundWorker on a timed basis. Here's an example:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.DoWork += isOnline;
isDbAvail.RunWorkerCompleted += rewriteOnlineStatus;
}
private void rewriteOnlineStatus(object sender, RunWorkerCompletedEventArgs e)
{
Subs.Connection connection = new Subs.Connection();
changeStatus(connection.isDbAvail());
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
System.Threading.Thread.Sleep(8000);
}
}
public void changeStatus(bool status)
{
if (status)
{
serverStatusVal.Text = "Connected";
serverStatusVal.ForeColor = System.Drawing.Color.DarkGreen;
}
else
{
serverStatusVal.Text = "Not connected";
serverStatusVal.ForeColor = System.Drawing.Color.Red;
}
}
What's happening here is that the isOnline method checks if there is a connection to the database (just an example) every 8 seconds and changes the text accordingly. What I've noticed though, is that the while loop inside the isOnline method causes the rewriteOnlineStatus method never to fire because it runs indefinitely. Is there another workaround to this?
I suggest you use BackgroundWorker.ReportProgress, and check connectivity in the background thread.
Something like this:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.WorkerReportsProgress = true;
isDbAvail.DoWork += isOnline;
isDbAvail.ProgressChanged += rewriteOnlineStatus;
isDbAvail.RunWorkerAsync();
}
private void rewriteOnlineStatus(object sender, ProgressChangedEventArgs e)
{
changeStatus((bool)e.UserState);
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
Subs.Connection connection = new Subs.Connection();
isDbAvail.ReportProgress(0, connection.isDbAvail);
System.Threading.Thread.Sleep(8000);
}
}
Now the BackgroundWorker is doing the work, and reporting back to the UI thread via ProgressChanged.
My form contain two controls: button1 and timer1
timer1.Interval=1000; timer1.Enable=true;
While click button1, application on windows will start. Ex:notepad will show.
But timer1 is not running while notepad is showing.
How to timer1 so running ??.
My code:
private void button1_Click(object sender, EventArgs e)
{
Process pro = new Process();
pro.StartInfo.FileName = "notepad";
pro.StartInfo.Arguments = "";
pro.Start();
pro.WaitForExit();
}
private void timer1_Tick(object sender, EventArgs e)
{
DateTime dtime = DateTime.Now;
string date_time = dtime.ToString("dd/MM/yyyy HH:mm:ss");
textBox2.Text = date_time;
}
From Process.WaitForExit:
Instructs the Process component to wait indefinitely for the associated process to exit.
Your timer is trying to invoke timer1_Tick, but your UI Thread is currently stuck waiting for the process to exit, which it wont.
You have two choices to work around this:
Simply remove the call to WaitForExit if you dont really need to wait
If you do need to be notified when the process exits, set Process.EnableRaisingEvents to true and register to the Process.Exited event
The WaitForExit() is "blocking" your interface from refreshing,the call just waits there for the process to exit. As an alternative if you need to do something when the process as exited do this:
private void button1_Click(object sender, EventArgs e)
{
Process pro = new Process();
pro.StartInfo.FileName = "notepad";
pro.StartInfo.Arguments = "";
//if you need to do something when the process exits do this:
pro.EnableRaisingEvents = true;
pro.Exited += new EventHandler(pro_Exited);
pro.Start();
//pro.WaitForExit();
}
void pro_Exited(object sender, EventArgs e)
{
//do what you need here...
}
Instead you could start the process with a BackGroundWorker.
pro.WaitForExit(); makes UI thread to freeze so it can't update.
To stop user from actions, you can disable some controls, while process is running. You can subscribe to process.Exited event and enable your controls, when user closes the process.
private void button1_Click(object sender, EventArgs e)
{
Process pro = new Process();
pro.StartInfo.FileName = "notepad";
pro.StartInfo.Arguments = "";
pro.Start();
button1.Enabled = false;
pro.EnableRaisingEvents = true;
pro.Exited += pro_Exited;
}
void pro_Exited(object sender, EventArgs e)
{
button1.Invoke((MethodInvoker) delegate { button1.Enabled = true; });
}
Update
As another answer suggested you should set EnableRaisingEvents property to true.
Also pro_Exited method will run in a different thread, so you need to use Control.Invoke method to change UI.
Update 2
If can't delete pro.WaitForExit(); you can use another timer, because System.Windows.Forms.Timer is running in UI thread and is blocked with it.
private System.Threading.Timer timer = new System.Threading.Timer(Callback);
public Form()
{
InitializeComponent();
timer.Change(0, 1000);
}
private void Callback(object state)
{
DateTime dtime = DateTime.Now;
string date_time = dtime.ToString("dd/MM/yyyy HH:mm:ss");
button1.Invoke((MethodInvoker)delegate { textBox1.Text = date_time; });
}
It will not update the textBox, when process is opened, but the timer will run and can do some work.
Update 3
In case of multiple processes you can count them and check number of active processes in pro_Exited method.
private volatile int activeProcessCount = 0;
private void pro_Exited(object sender, EventArgs e)
{
activeProcessCount--;
if (activeProcessCount == 0)
{
button1.Invoke((MethodInvoker) delegate { button1.Enabled = true; });
}
}
private void button1_Click(object sender, EventArgs e)
{
//code
activeProcessCount = 2;
pro1.Start();
pro2.Start();
}
This case is using C# WPF. I want to instantly disable a button after clicking it to prevent clicking it twice in short succession. I disabled the button in OnClick_Event but still clickable.
Part of source is as below.
private void Button_Click_UpdateBurndownChart(object sender, RoutedEventArgs e)
{
if(threadNotWorking)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
private void startWorkThread()
{
... ...
//after finish required process
updateButton.IsEnabled = true;
}
Is there any way to accomplish this?
you may want to use a dispatcher, there is probably a threading problem (callback function running on seperate thread and trying to access ui which runs on another thread). try this . .
updateButton.Dispatcher.BeginInvoke(
new ThreadStart(() => updateButton.IsEnabled = false),
System.Windows.Threading.DispatcherPriority.Input, null);
instead of
updateButton.IsEnabled = false;
What happens if you were instead to change the order of your events from:
updateButton.IsEnabled = false;
startWorkThread();
To
startWorkThread();
updateButton.IsEnabled = false;
Let me know how this goes.
What it looks like is that you are starting your thread then immediatly enabling your button before your thread has finished. You would be better off using a BackgroundWorker and enable your Button in the RunWorkerCompleted Event. Though you can do something similar by enabling your button using a BeginInvoke at the end of your Process.
public void doWork()
{
System.Threading.Thread.Sleep(10000); //Simulating your Process
Dispatcher.BeginInvoke(new System.Threading.ThreadStart(delegate() { updateButton.IsEnabled = true; }), System.Windows.Threading.DispatcherPriority.Background);
}
Example with BackgroundWorker
using System.ComponentModel;
public partial class MainWindow : Window
{
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
updateButton.IsEnabled = true;
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(10000); //Simulating your work
}
private void startWorkThread()
{
bgw.RunWorkerAsync();
}
private void updateButton_Click(object sender, RoutedEventArgs e)
{
if (bgw.IsBusy != true)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
}