I have already tried several online examples (Thread, Dispatcher, await/async) but none is working for me in my C#/WPF project.
I have the following button click method:
private void BtnInstall_Click(object sender, RoutedEventArgs e)
{
this.lblResponse.Content = "";
executeInstall(); //do some work
this.lblResponse.Content = "DONE";
}
The label gets updated afterwards to DONE, but when I click again on the button the label isnt getting emptied before the processing of executeInstall.
As I mentioned I already tried several different examples from other questions (Dispatcher.BeginInvoke, Thread, Task, await/async) but none of them has worked - the label change before is never done before the processing of executeInstall.
I am working in .NET framework 4.7.2.
Is there maybe a setting that debug mode only executes the program with one thread and thats maybe why none of the solutions works for me?
Use async for that.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
this.lblResponse.Content = "";
await Task.Run(()=> executeInstall());
this.lblResponse.Content = "DONE";
}
UPDATE: If you need to access the UI inside your executeIntall method you will need to invoke the Dispatcher. In this case you would need to delay the Task to give the label time to update before the install starts. Note that this will cause the UI to freeze during the entire install.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
lblResponse.Content = "starting...";
await Task.Delay(100).ContinueWith(_=>
{
App.Current.Dispatcher.Invoke(() =>
{
executeInstall();
lblResponse.Content = "DONE";
});
});
}
A better approach would be to only call the dispatcher when it's actually needed. This would keep the UI responsive during the entire process.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
lblResponse.Content = "starting...";
await Task.Run(()=> executeInstall());
lblResponse.Content = "DONE";
}
private void executeInstall()
{
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Downloading Files...");
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Unzipping Files...");
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Updating Files...");
Thread.Sleep(1000); //do time consuming operation
}
Related
I am new to Async and Await and have created a simple project in order to understand how it works.
For this, I have a simple Windows Form application that has 2 elements:
Get Completed Items button
TextBox showing all Completed Items retrieved
When I click the button, it should display all completed Items in the TextBox.
This is the code I have written:
private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
QueueSystem queueSystem = QueueSystem.NewInstance(75);
Stopwatch watch = new Stopwatch();
watch.Start();
await Task.Run(() => GetCompletedItems(queueSystem));
watch.Stop();
lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";
}
private void GetCompletedItems(QueueSystem queueSystem)
{
foreach (var item in queueSystem.GetCompletedItems())
{
txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
}
}
However, I am getting an error in
txtItems.Text +=
$"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
The error says
Additional information: Cross-thread operation not valid: Control
'txtItems' accessed from a thread other than the thread it was created
on.
I checked in Debug and a new thread was created for GetCompletedItems(). When I read about Async and Await, I read that it doesn't necessarily create a new thread but it seems to have created a new one for some reason.
Is my implementation and understanding of Async and Await wrong?
Is it possible to use Async and Await in a Windows Forms application?
You cannot access UI thread on a different thread. This should help
private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
QueueSystem queueSystem = QueueSystem.NewInstance(75);
Stopwatch watch = new Stopwatch();
watch.Start();
var results = await Task.Run(() => queueSystem.GetCompletedItems());
foreach (var item in results)
{
txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
}
watch.Stop();
lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";
}
You can access the thread from another thread in a following way. It does helps to avoid the cross thread exception in your application.
private void Thread()
{
this.Invoke((System.Action)(() => {
//your thread call or definition
});
}
When I read about Async and Await, I read that it doesn't necessarily create a new
thread
This is true for regular async methods. Consider this:
private async void button1_Click(object sender, EventArgs e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await DoesNothing();
}
private async Task DoesNothing()
{
// outputs the same thread id as similar line as from above;
// in particlar, for WinForms this means, that at this point
// we are still at UI thread
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
}
but it seems to have created a new one for some reason
This is what Task.Run is intended for:
Queues the specified work to run on the ThreadPool
In other words, it pushes anything you pass it as a delegate to a thread pool thread. Since we are in WinForms, this means, that anonymous method () => GetCompletedItems(queueSystem) will be executed at thread pool thread, not at UI one.
Here's code sample from above with little change:
private async void button1_Click(object sender, EventArgs e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Run(DoesNothing);
}
private async Task DoesNothing()
{
// outputs DIFFERENT thread id;
// in particlar, for WinForms this means, that at this point
// we are not at UI thread, and we CANNOT access controls directly
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
}
I have a timer on WinForm which I start when the form loads:
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (true)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
The problem is when I start the app in Debug mode in VS and the close it. I get an ObjectDisposedException which states that my form is already disposed.
I tried to fix it the following way:
private bool _runningTimer = true;
public MainForm()
{
InitializeComponent();
// ...
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
_runningTimer = false;
}
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (_runningTimer)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
But the problem still ocurrs. What Am I doing wrong here?
UPDATE: I know that there is a standart timer for WinForms that works great in multithreaded invironment. I just wanted to know how it is possible to make it work to better understand how to deal with race conditions. This kind of timer is just an example, it could be another process that needs to update GUI.
UPDATE 2: Following the Hans Passant and Inigmativity answers I came to that code:
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
Task task = new Task(async () => {
while (!IsDisposed)
{
Invoke(action);
await Task.Delay(1000);
}
});
task.Start();
}
But anyway if I make time interval, for example 100ms, the ObjectDisposedException still throws.
This is not real life example, I just experimenting with it...
In your first example the Task has no idea your app is exiting and is at risk of invoking the action after the label is destroyed, hence the ObjectDisposedException.
Though you attempt to alert the task in the second example, it isn't really that thread-safe and you could still invoke the action after the control is disposed.
Timers
A better solution is to just use a WinForms Timer. If you place the timer on the form via the designer, it automatically registers it as a component dependency making lifetime management much easier.
With WinForm timers you don't need to worry about threads or Tasks and more importantly you won't need to worry about Invoke if you need to update the UI (as opposed to child threads or non-UI context tasks)
Tell me more
How to: Run Procedures at Set Intervals with the Windows Forms Timer Component
Ok, I tried to use task cancellation the following way:
public MainForm()
{
InitializeComponent();
cts = new CancellationTokenSource();
Load += MainForm_Load;
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Cancel();
}
private void MainForm_Load(object sender, EventArgs e)
{
CancellationToken ct = cts.Token;
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
var task = Task.Factory.StartNew(async () => {
ct.ThrowIfCancellationRequested();
while (true)
{
Invoke(action);
await Task.Delay(100);
}
}, ct);
}
Don't know whether it's right but it seems works even if the time interval set to 10 ms.
I am doing an c# small app with a richtextbox and a button that will check if some files exists and display the result into the richtextbox.
private void RunButton_Click(object sender, EventArgs e)
{
myRichTextBox.AppendText("Starting...");
if (checkFileExists())
{
myRichTextBox.AppendText("File exists.");
}
else
{
myRichTextBox.AppendText("File NOT exists!");
}
}
The problem I am facing is that the text "Starting..." is appended into the richtextbox when the checkFileExists() has finished. Since this one can take some time I would like to display the "Starting" message at the beginning and the rest of text messages when checkFileExists has finished.
Can you help me with that?
thanks in advance!
You need to set the text on the main thread, then start the long running process (checking whether the file exists) on a separate thread. Once that's finished you can update the text with the result - but that would have to be marshalled back to the main (UI) thread.
Here's one way to do it.
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Starting...";
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => CheckIfFileExists(uiScheduler));
}
private async Task CheckIfFileExists(TaskScheduler uiScheduler)
{
await Task.Delay(2000);
var exists = true; // check if exists
await Task.Factory.StartNew(() => UpdateText(exists), CancellationToken.None, TaskCreationOptions.None, uiScheduler).ConfigureAwait(false);
}
private void UpdateText(bool exists)
{
button1.Text = $"Exists: {exists}.";
}
I'm trying to update the text of some GUI controls from a second thread but Visual Studio shows an exception:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information:
Invalid operation through sub-processes: access to 'impPROF_combo' took control from a other thread than that in which he created .
My code is:
private async void checkBox1_CheckedChanged(object sender, EventArgs e)
{
sciLoadingCircle1.Visible = true;
sciLoadingCircle1.Active = true;
await Task.Run(() => refreshComboColumnsNames());
sciLoadingCircle1.Visible = false;
sciLoadingCircle1.Active = false;
}
private void refreshComboColumnsNames()
{
object[] columnsNames = this.generateComboColumnsNames();
int impPROF_combo_selected = impPROF_combo.SelectedIndex; //the exceptions throws from here
impPROF_combo.Items.Clear();
impPROF_combo.Items.AddRange(columnsNames);
impPROF_combo.SelectedIndex = impPROF_combo_selected;
}
How I can do this the right way?
Thank you.
UI components should only be accessed from their UI thread. In this case, you're putting too much code in the Task.Run:
private async void checkBox1_CheckedChanged(object sender, EventArgs e)
{
sciLoadingCircle1.Visible = true;
sciLoadingCircle1.Active = true;
await refreshComboColumnsNamesAsync();
sciLoadingCircle1.Visible = false;
sciLoadingCircle1.Active = false;
}
private async Task refreshComboColumnsNamesAsync()
{
object[] columnsNames = await Task.Run(() => this.generateComboColumnsNames());
int impPROF_combo_selected = impPROF_combo.SelectedIndex;
impPROF_combo.Items.Clear();
impPROF_combo.Items.AddRange(columnsNames);
impPROF_combo.SelectedIndex = impPROF_combo_selected;
}
In order to update GUI (or use) elements from another thread you should use Control.BeginInvoke() https://msdn.microsoft.com/ru-ru/library/system.windows.forms.control.begininvoke(v=vs.110).aspx
And here is how to use it.
How to use BeginInvoke C#
Usual reason why you want to execute some code in a thread other than GUI thread is that the operation takes to much time to finish and blocks GUI thread.
I can see in refreshComboColumnsNames method the only line of code, which can potentially take time - generateComboColumnsNames. Other statements are GUI update operations, which have to be executed in GUI thread. As I understand, generateComboColumnsNames does some heavy calculations or executes db query or sends http request or does any other long operations. Result of this operation needs to be displayed in UI. Correct me, if I am wrong.
I would suggest to make generateComboColumnsNames async operating, which is executed in separate thread. This operation must not update UI - just return data.
Result will look like next:
private async void checkBox1_CheckedChanged(object sender, EventArgs e)
{
sciLoadingCircle1.Visible = true;
sciLoadingCircle1.Active = true;
await refreshComboColumnsNames();
sciLoadingCircle1.Visible = false;
sciLoadingCircle1.Active = false;
}
private async Task refreshComboColumnsNames()
{
object[] columnsNames = await this.generateComboColumnsNames();
int impPROF_combo_selected = impPROF_combo.SelectedIndex;
impPROF_combo.Items.Clear();
impPROF_combo.Items.AddRange(columnsNames);
impPROF_combo.SelectedIndex = impPROF_combo_selected;
}
The following Code does not change the Text and stops executing the Task
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
Task.Run(() => MyAsyncMethod());
}
public async Task MyAsyncMethod()
{
label1.Text = "";
//everything from here on will not be executed
}
would be really handy if you could use async together with the UI
for accessing a GUI control through a second thread you need to invoke.
following example shows how to set a label's text properly
private void setLabel1TextSafe(string txt)
{
if(label1.InvokeRequired)
label1.Invoke(new Action(() => label1.Text = txt));
else
label1.Text = txt;
}
I hope this solves your problem
would be really handy if you could use async together with the UI
The design of async was carefully done so you can use it naturally with the UI.
in my code i run a function that does a lot of IO and stuff that takes a long time
If you have asynchronous I/O methods (which you should), then you can just do this:
private async void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
await MyMethodAsync();
}
public async Task MyMethodAsync()
{
label1.Text = "";
await ...; // "lot of IO and stuff"
label1.Text = "Done";
}
That's the most natural approach.
However, if you need to run code on a background thread (e.g., it's actually CPU-bound, or if you just don't want to make your I/O operations asynchronous like they should be), then you can use IProgress<T>:
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
var progress = new Progress<string>(update => { label1.Text = update; });
await Task.Run(() => MyMethod(progress));
}
public void MyMethod(IProgress<string> progress)
{
if (progress != null)
progress.Report("");
...; // "lot of IO and stuff"
if (progress != null)
progress.Report("Done");
}
Under no circumstances should modern code use Control.Invoke or (even worse) Control.InvokeRequired.
Task.Run is used to envelope an Action (that is not async) into a Task. Any Task that you want to execute should be awaited. Thus, that Task.Run of yours is rigorously doing nothing.
Mark that button1_Click event handler of yours as async. Then remove that Task.Run and instead do await MyAsyncMethod().
Try this. You don't need to fire a new thread to invoke the async method. compiler will do it for you.
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
MyAsyncMethod();
}
public async Task MyAsyncMethod()
{
return await Task.Run(() =>{
label1.Text = "";
//everything from here on will not be executed
}
}
I think both the questions and some of the answers are not clear. Depending on where in the task thread you need to update the label you have to use invoke. Otherwise you can leverage await and leverage the standard semantics.
private async void button1_Click(object sender, EventArgs e)
{
label1.Text = "Starting to run a long task... carry on...";
await snooze(3);
label1.Text = "Done with long task";
}
public Task<int> snooze(int seconds)
{
label1.Text = "zzzz...";
return Task.Run(
() => {
label1.Invoke(new Action(() => label1.Text = "For some reason I need to alert you here.. bad dream perhaps...direct access to label1 will fail"));
Thread.Sleep(seconds * 1000);
return seconds;
});
}