How to change label.text use Task.Run() - c#

No work await Task.Run():
private async void button2_Click(object sender, EventArgs e)
{
await Task.Run(() => {
monitor_r(label1);
});
}
protected async Task monitor_r(Label L1)
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
}
These commands
MessageBox.Show(L1.Name);
and
L1.ForeColor = Color.Blue;
works fine but
L1.Text = "test";
does not work.
Can you help, why do not change a Label Text?

Try Control.Invoke: we should run Winform UI in the main thread only
protected async Task monitor_r(Label L1)
{
Action action = () =>
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
};
if (L1.InvokeRequired)
L1.Invoke(action); // When in different thread
else
action(); // When in the main thread
}

If you're on debug mode, take a look at the output window. It should shows exception message something like this:
System.InvalidOperationException' in System.Windows.Forms.dll.
That because label1 accessed from a thread other than the thread it was created on. And it will causing invalid cross-thread operation.
You can solve this by using Control.Invoke as Dmitry Bychenko already mentioned. Here is simple extension to make thread-safe calls to Winforms Control.
public static void TryInvoke(this Control control, Action<Control> action)
{
if (control.InvokeRequired) control.Invoke(new Action(() => action(control)));
else action(control);
}
Sample usage
label1.TryInvoke(x => x.Text = "test");
label1.TryInvoke(x => x.ForeColor = Color.Blue);
Or
this.TryInvoke(x =>
{
label1.Text = "test";
label1.ForeColor = Color.Blue;
});
Secondly, since you don't await anything at monitor_r, i'd recommend to use void instead of async Task.
Even if you're await something at monitor_r you don't need
await Task.Run(() => {
monitor_r(label1);
});
..because monitor_r itself is a task. So just call await monitor_r(label1);

If you wish to have a separate thread, you can try this using BackgroundWorker. You can implement the ReportProgress if you have a loop.
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(label1.Name);
label1.ForeColor = Color.Blue;
label1.Text = "test";
}

Related

c# winforms my ui still freeze with async in event load

In my event load of my form , I call a method loadDg:
private void form_Load(object sender, EventArgs e)
{
loadDg();
}
and
private async Task loadDg()
{
pictureLoading.Visible = true;
await Task.Run(() => { string[] datas = db.row("select * from products");
string[] datas2 = db.row("select * from users");
double one = Convert.ToInt32(datas[0]);
label1.Text = one.toString();
//....
});
pictureLoading.Visible = false; //hide gif animation
}
in my code , db.row This method always returns only 1 row ( string array) , but my ui freezes still , i try update UI continuously with async without freeze at startup
There is nothing to prevent your code run asynchronously. pictureLoading will be invisible even before task is completed. You should fix cross-thread problem and logic of the UI as this:
private void form_Load(object sender, EventArgs e)
{
pictureLoading.Visible = true;
loadDg();
}
private async Task loadDg()
{
await Task.Run(() =>
{
string[] datas = db.row("select * from products");
string[] datas2 = db.row("select * from users");
double one = Convert.ToInt32(datas[0]);
label1.BeginInvoke((Action)delegate ()
{
label1.Text = one.toString();
//hide gif animation
pictureLoading.Visible = false;
});
//....
});
}
Unnecessarily jumping between threads/context should be avoided.
This is an with better resource usage:
private async void form_Load(object sender, EventArgs e)
{
pictureLoading.Visible = true;
try
{
label1.Text = await LoadDgAsync();
}
catch
{
// error handling
}
finally
{
pictureLoading.Visible = false;
}
}
private Task<string> LoadDgAsync()
{
return Task.Run(() =>
{
string[] datas = db.row("select * from products");
string[] datas2 = db.row("select * from users");
double one = Convert.ToInt32(datas[0]);
//....
return one.toString();
});
}
You are calling the loadDg() function synchronously.
Unless you await the loadDg() function call (since its return type is Task) and make the form_Load function asynchronous the function call will be synchronous.
The correct way to fix it is...
private async void form_Load(object sender, EventArgs e)
{
await loadDg();
}

Can't access winforms label after await?

I have a long running method which I made async. I made my button click handler async as well, but when I try to access my label in my button click after the long method is done, it tells me it can't can't access it from another thread. Here is the code:
private void Migrate()
{
for (int i = 2; i <= excelData.GetUpperBound(0); i++)
{
var poco = new ExpandoObject() as IDictionary<string, object>;
foreach (var column in distributionColumnExcelHeaderMappings)
{
if (column.ColumnIndex > 0)
{
var value = excelData[i,column.ColumnIndex]?.ToString();
poco.Add(column.DistributionColumnName.Replace(" ", ""), value);
}
}
pocos.Add(poco);
}
migrationRepository.BulkInsert(insertToTable, "Id", pocos);
}
private async void btnMigrate_Click(object sender, EventArgs e)
{
Task task = new Task(()=> Migrate());
task.Start();
lblStatus.Text = "Migrating data....";
await task;
lblStatus.Text = "Migration Complete";
}
When the button is clicked, I see the status Migrating data..... When that is complete, it throws an error on lblStatus.Text = "Migration Complete". I thought after await, it goes back to the UI thread?
I cleared out most of the code and it still throws the same error. This is a VSTO excel add-in. Could that be part of the problem?
private void Migrate()
{
}
private async void btnMigrate(object sender, EventArgs e)
{
Task.Run(()=>Migrate());
lblStatus.Text = "Done"; //still get error here
}
Try and update your code to the following:
Instead of creating your task and then starting it manually, update it to just await on Task.Run:
private async void btnMigrate_Click(object sender, EventArgs e)
{
lblStatus.Text = "Migrating data....";
await Task.Run(()=> Migrate());
lblStatus.Text = "Migration Complete";
}
Edit:
You can use a helper method that will check to see if the label needs to be invoked before updating.
private async void btnMigrate_Click(object sender, EventArgs e)
{
SetLabelText(lblStatus, "Migrating data....");
await Task.Run(()=> Migrate());
SetLabelText(lblStatus, "Migration complete.");
}
private void SetLabelText(Label label, string text)
{
if (label.InvokeRequired)
{
label.BeginInvoke((MethodInvoker) delegate() {label.Text = text;});
}
else
{
label.Text = text;
}
}

Can i add a callback to a BackgroundWorker that is already running?

Is it possible to add a callback to a background worker while it is running ?
bw.DoWork += new DoWorkEventHandler( some callback );
bw.RunWorkerAsync();
bw.DoWork += new DoWorkEventHandler( some callback );
Thank you.
Yes you can as it's only a subscription to an event but you can't run bw until he has completed the execution of the first task
here an example to illustrate this the following code will show an InvalidOperationException telling This BackgroundWorker is currently busy and cannot run multiple tasks concurrently."
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerAsync();
backgroundWorker1.DoWork+=new DoWorkEventHandler(backgroundWorker2_DoWork);
//at this line you get an InvalidOperationException
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
do
{
} while (true);
}
void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
do
{
} while (true);
}
}
As an answer to your comment question
#SriramSakthivel Thanks. Is there a way to put tasks in a queue ?
yes you can if you are using .net 4.0 you can use task with ContinueWith and attach it to your UI
taskScheduler it will have the same behavior as if you are using BackgroundWorker
private void TestButton_Click(object sender, EventArgs e)
{
TestButton.Enabled = false;
var uiThreadScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var backgroundTask = new Task(() =>
{
Thread.Sleep(5000);
});
var uiTask = backgroundTask.ContinueWith(t =>
{
TestButton.Enabled = true;
}, uiThreadScheduler);
backgroundTask.Start();
}

Display the current variable value while looping

private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
string IDs = ID.Text;
string[] eachIDs = Regex.Split(IDs, "\n");
foreach (var eachID in eachIDs)
{
getContent(eachID);
titleBox.Text = "Done";
}
}
private void getContent(string value)
{
label1.Text = value;
Thread.Sleep(5000);
}
I will give 4 id's as Input say "IDNUMBER01, IDNUMBER02, IDNUMBER03, IDNUMBER04" each in a new line in Rich Text Box.
The code splits them successfully. I want to show the Value of the ID being used in the current loop in a Label Text.
Problem with my code is it shows only the last ID which goes through the loop.
Probably your UI freezing and you can't see the changes.Try this, use async/await feature:
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
string IDs = ID.Text;
string[] eachIDs = Regex.Split(IDs, "\n");
foreach (var eachID in eachIDs)
{
await getContent(eachID);
titleBox.Text = "Done";
}
}
private async Task getContent(string value)
{
label1.Text = value;
await Task.Delay(5000);
}
This is because the UI is only Updated after the execution of this code, since they are executing in the same thread. You will need to open a thread, run this code, and call the dispatcher (or the Control.BeginInvoke if this app is Winforms) to update the UI.
EDIT
Try this:
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
string IDs = ID.Text;
new System.Threading.Thread(() =>
{
string[] eachIDs = Regex.Split(IDs, "\n");
foreach (var eachID in eachIDs)
{
getContent(eachID);
titleBox.BeginInvoke((Action) delegate { titleBox.Text = "Done"; });
}
}).Start();
}
private void getContent(string value)
{
label1.BeginInvoke((Action) delegate { label1.Text = value; });
Thread.Sleep(5000);
}
In your example, you'd be better using a timer to display your value text. You're only seeing the last ID because the loop is executing very quickly, and using Thread.Sleep within the foreach isn't going to fly.
You could use Application.DoEvents() before the Thread.Sleep, but a timer is still your better option ... imho.

Why is a disabled button clickable?

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();
}
}
}

Categories